1. Tabla de contenidos

  1. Introducción.

  2. Presentación de la serie de datos.

  3. Análisis descriptivo de la base de datos.

  4. Metodología a desarrollar en la práctica.

  5. Análisis preliminar de datos, y primer EDA realizado en Excel.

  6. Tratamiento de datos en RStudio.

  7. Feature engineering.

  8. Selección de variables.

  9. Algoritmos de la librería Caret.
    10.1. Random Forest.
    10.2. Modelo Multinomial.
    10.3. Modelo Xgboost.
    10.4. Modelo Red Neuronal.
    10.5. Validación cruzada repetida (VCR) de los modelos de Caret.

  10. Algoritmos de la librería H2O.
    11.1. Modelo Gradient Boosting(GBM).
    11.2. Red Neuronal Profunda.
    11.3. Ensamblado (stacking) en H2O.
    11.4. Validación cruzada repetida (VCR) de los modelos de H2O.

  11. Comparación Caret vs H2O.

  12. Predicciones sobre el set de test / evaluación final.

  13. Conclusiones.

  14. Referencias.

  15. Anexos.

2. Introducción.

Como requerimiento necesario para la obtención del grado de Master en Big Data and Business Analytics de la UCM, este trabajo se enmarca en la opción 1 que corresponde al análisis de un set de datos.

En este sentido, me sentí motivado a buscar una base de datos que me permitiera desarrollar un algoritmo de machine learning para un fin específico. La base encontrada correspondió a una colección de información acerca de reservas de hotel, donde mi trabajo se centró en la predicción de la tasa de cancelación de estas reservas. Desde un punto de vista de negocios, la predicción de este hecho particular permitiría a una empresa balancear mejor su capacidad instalada por medio de acciones de revenue management (promoción y pricing) para no ver afectados sus beneficios de manera sorpresiva.

Para cumplir el objetivo, mi trabajo se utilizó herramientas cubiertas en distintos módulos del Máster como minería de datos, machine learning, deep learning entre otros. Respecto de herramientas específicas se usó completamente RStudio y las librerías Caret y H2O para el desarrollo de modelos. Para análisis preliminar de datos se utilizó BI Data Studio de Google. Para el entrenamiento de modelos que requerían mayor potencia de máquina se utilizó la instancia de Compute Engine de Google con Rstudio Server.

La metodología de trabajo correspondió a la estándar aprendida en el módulo de machine learning, que implica el desarrollo de algoritmos individuales, tuneo de hiperparámetros y comparación entre modelos por medio de validación cruzada repetida. De estas comparaciones se seleccionó un mejor algoritmo para el set de datos.

Es importante destacar que no es objetivo de la presente práctica el participar en un concurso de machine learning.

El presente informe está redactado desde un punto de vista descriptivo, gráficas de soporte y código de R se incluye como anexos.

La presente práctica se subió, como HTML, a repositorio de gitHub.

3. Presentación de la serie de datos.

La serie de datos seleccionada corresponde a una base de datos relacionada con diversa información recolectada acerca de reservas de hotel. La base de datos puede descargarse del repositorio de kaggle.com: https://www.kaggle.com/jessemostipak/hotel-booking-demand.

La base completa consta de 119.390 registros y 34 campos.

El detalle de los campos existentes en la base de datos, es el siguiente:

a) hotel (factor 2 niveles): Representa el tipo de hotel en el que se reserva alojamiento. Dominio: City_Hotel y Resort_Hotel.

b) is_canceled (factor 2 niveles): Representa si una reserva fue cancelada o no por el cliente. Dominio: 1 = Reserva fue cancelada y 0 = Reserva no fue cancelada.

c) lead_time (numérica, multinivel): Representa el número de días de anticipación con que el cliente reservó alojamiento en un hotel determinado.

d) arrival_date_year (factor, 3 niveles): Corresponde al año de ingreso del cliente al hotel (check-in). Dominio: 2015, 2016 y 2017.

e) arrival_date_month (factor, 12 niveles): Corresponde al número correspondiente al mes de ingreso del cliente al hotel (check-in). Dominio: 1 a 12.

f) arrival_date_week_number (factor, multinivel): Corresponde al número de semana en que el cliente haría ingreso al hotel. Dominio: 1 a 53.

g) arrival_date_day_of_month (factor, multinivel): Corresponde al día del mes en que el cliente haría ingreso al hotel. Dominio: 1 a 31.

h) stays_in_weekend_nights (numérica, multinivel): Corresponde al número de noches de la semana (lunes a viernes) en que el cliente alojaría en el hotel.

i) stays_in_week_nights (numérica, multinivel): Corresponde al número de noches de fin de semana (lunes a viernes) en que el cliente alojará en el hotel.

j) adults (numérica, multinivel): Corresponde al número de adultos para el que se requiere reserva de alojamiento.

K) children (numérica, multinivel): Corresponde al número de niños para los que se requiere reserva de alojamiento.

l) babies (numérica, multinivel): Corresponde al número de bebés (-2 años) para los que se requiere reserva de alojamiento.

m) meal (factor, 4 niveles): Corresponde al tipo de alimentos que se han contratado junto con la reserva. Dominio: Bead_Breakfast, Half_Board1, Full_Board2 y No_Meal. Este campo presenta valores perdidos.

n) country (factor, multinivel): Corresponde al país del cliente que realiza la reserva en el hotel.

m) Long_av (numérica, multinivel): Corresponde al valor de la longitud promedio del país de procedencia del cliente que se reserva en el hotel.

o) Lat_av (numérica, multinivel): Corresponde al valor de la latitud promedio del país de procedencia del cliente que se reserva en el hotel.

p) market_segment (factor, 7 niveles): Corresponde al segmento de mercado al que pertenece el cliente. Dominio: Aviation, Corporate, Complementary, Direct, Groups, Offline TravelAgens_TurismOperator (TA_TO), Online TA_TO.

q) distribution_channel (factor, 4 niveles): Corresponde al canal de distribución del servicio del hotel. Dominio: Corporate, Direct, GDS (Global Distribution Service) y TA_TO.

r) is_repeated_guest (factor, 2 niveles): Corresponde al identificador si el cliente ha reservado en el pasado o no. Dominio: 1 = Si, el cliente ha reservado en el pasado y 0 = No, el cliente no ha reservado en el pasado.

s) previous_cancellations (numérica, multinivel): Corresponde al identificador del número de reservas canceladas que realizado el cliente en el pasado.

t) previous_bookings_not_canceled (numérica, multinivel): Corresponde al identificador del número de reservas no canceladas que ha realizado el cliente en el pasado.

u) reserved_room_type (factor, 16 niveles): Corresponde a la categoría de habitación reservada por el cliente. Dominio: A a P3.

v) assigned_room_type (factor, 16 niveles): Corresponde a la categoría de habitación finalmente asignada al cliente al momento del check-in. Dominio: A a P4.

w) booking_changes (numérica, multinivel): Corresponde al número de cambios realizados a la reserva de un cliente desde el momento de su ingreso en sistema, a la fecha del check-in en counter.

x) deposit_type: (factor, 3 niveles): Corresponde al tipo de depósito realizado para garantizar la reserva. Dominio: Refundable, Non_Refundable, No_Deposit.

y) agent (numérica, multinivel): Corresponde al código del agente que realiza la reserva en sistema.

z) company (factor, multinivel): Corresponde a la compañía en la que trabaja el cliente que realiza la reserva. La base no tiene nombres de compañías por un tema de privacidad de datos.

aa) days_in_waiting_list (numérica, multinivel): Corresponde al número de días en que un cliente se encuentra en lista de espera por una reserva.

ab) customer_type (factor, 4 niveles): Corresponde al tipo de cliente que realiza la reserva en el hotel. Dominio: Transient5, Transient_party6, Contract7, Group8.

ac) Av_DailyRate (numérica, multinivel): Corresponde al valor por noche, y por persona, en base a la habitación reservada.

ad) required_car_parking_spaces (numérica, multinivel): Corresponde al número de plazas de estacionamiento requeridas en la reserva de habitación.

ae) total_of_special_requests (numérica, multinivel): Corresponde al número de requerimientos especiales realizados por el cliente para la reserva de habitación.

af) reservation_status (factor, 3 niveles): Corresponde a la variable objetivo a predecir. Dominio: Check_Out = Reserva fue ejecutada como estaba planificada, Canceled = Reserva anulada/cancelada antes del check-in y No_Show = Cliente no se presentó en el counter en la fecha prevista. A diferencia de la variable “is_canceled”, consideraré esta variable como objetivo por cuanto la categoría “No_Show” no puede atribuirse a una cancelación de la reserva, como a mi parecer erróneamente, se interpreta la mencionada variable. Se trata pues, no de un problema de clasificación binaria, sino, de un problema de clasificación multiclase.

ag) reservation_status_date (fecha, multinivel): Corresponde a la fecha de la última actualización de la información de la reserva en el sistema del hotel.

4. Análisis descriptivo de la base de datos.

Previo desarrollo de los algoritmos creímos necesario obtener una comprensión general de los datos a tratar. En este sentido, una de las preguntas iniciales realizadas fue quién es el cliente que está representado en los datos de la base?.

Los datos nos indican que es un cliente mayoritariamente de origen europeo. Los 5 principales países representados en la base de datos son Portugal, UK, Francia, España y Alemania. Esto ya es un tema, por cuanto vemos que casi no hay representación de personas de América o Asia; sino que además dentro de Europa, el cliente mayoritario es de Portugal9.

La base representa, mayoritariamente, a un viajero de negocios, dado que es una persona que: busca alojamientos de corta estancia (+95% de los datos), habitaciones que cuentan solo con facilidades básicas para dormir, que solo incluyen desayuno (77% de los datos) y en hoteles de ciudad (66% de los datos); en lugar de resorts que explicarían un cliente que busca alojamiento por vacaciones10.

Adicionalmente del viajero representado en los datos, podemos decir que por lo general: viaja solo, su estadía media en un hotel es de app 2,5 noches en días de semana11. En este punto, podríamos decir, aunque los datos no lo indican, su estadía de fin de semana es el domingo, día de check-in para comenzar una semana laboral el lunes temprano.

El gasto medio en habitación, por noche, es cercano a los USD 100.

Analizando los datos desde el punto de vista del negocio hotelero, el cliente que reserva, viene por primera vez en un 97% de los casos. Dado esto, no tiene historial de cancelaciones previas, lo cual es un problema para el el desarrollo de un algoritmo que pretenda pronosticar la probabilidad de cancelación de una reserva en base al historial del cliente.

Por otra parte, el viajero de negocios reserva en su mayoría (+67% de los casos) vía terceros, ya sea agencias de viaje u operadores turísticos. Por las reservas en su gran mayoría, se realizan pagos para garantizarlas (87% de los casos)12.

Respecto del check-in de las reservas, podemos ver que en su mayoría se producen en los meses de Agosto, Julio y Mayo, lo que coincide con el período de vacaciones europeo (particularmente los 2 primeros meses). Esto es extraño, por cuanto se esperaría que los meses más altos para el viajero de negocios no sean los meses de verano. Aun así, existen 5 meses con ingresos de app 10K personas mes: Octubre, Abril, Junio, Septiembre y Marzo.

Con relación al día del mes en el que se produce el check-in con más frecuencia, no hay un patrón claro en esta variable. Si podemos ver una baja relevante a mediados de cada mes, lo que puede ser indicativo de la existencia de un dato atípico que esté afectando la base13.

Al mirar la evolución de las reservas por año, no damos cuenta que 2015 tiene 22K registros, 2016 56K registros y 2017 app. 41K. No sabemos el motivo por el que se presenta este desbalance o cambio de un año a otro, no podemos inferir nada acerca de los motivos del incremento de los números de 2016 vs 2015, ni tampoco de la reducción que se muestra en 2017. Sin embargo, las cancelaciones cada año, fluctúan entre el 35% y el 38% del total de reservas. Las variaciones en esta cifra, tampoco podemos atribuirla a un motivo especial. No tenemos más información en los datos14.

Al abrir la información de reservas por tipo de hotel, notamos que hay una diferencia en la media de reservas realizadas en resorts versus hoteles de ciudad, de app 1 día. El viajero de negocios se hospeda, en promedio 2 noches en hoteles de la ciudad, mientras que el viajero que se hospeda en resorts lo hace, en promedio, 3 noches. El cliente que más tiempo se aloja es el tipo “contract” que lo hace 6 días en resorts. Este cliente sube la media15.

Al mirar la información del status de las reservas (variable objetivo), vemos que un 63% ha llegado a término, un 36% se canceló, y solo un 1% correspondió a reservas confirmadas, en las que el cliente no se presentó en el counter. Esta situación nos muestra una dificultad adicional al momento de construir un algoritmo, en el sentido que estamos lidiando con un desbalance en una variable clave en el entrenamiento de un modelo. Más adelante veremos cómo abordar este punto16.

Al mirar específicamente las cancelaciones, podemos decir que los clientes que más cancelan son los viajeros individuales, esto no sorprende del todo, por cuanto son los mayores viajeros en la base. Sin embargo, las cifras muestran que, en 2015, los viajeros individuales representaron solo el 50% del total de cancelaciones, sin embargo, en 2016 éstos representaron el 87% de las cancelaciones, y en 2017 el 93% de ellas. Esta diferencia respondió a un aumento de los viajes en 2016 y 201717, o es un problema de los datos en 2015?. De acuerdo a los datos de la base, no tenemos la respuesta a esta interrogante18.

Al abrir las cancelaciones por país, no sorprende ver que los primeros lugares los ocupen países que más viajeros muestran en la base: Portugal, UK, España, Alemania y Francia. Si sorprende que el quinto lugar en cancelaciones sea Italia, que en términos de frecuencias de viaje estaba más allá que el lugar 1019.

Respecto de las cancelaciones por tipo de hotel, ellas son app 3,2 veces mayores en los hoteles de ciudad que en los resorts, lo cual es coherente con el tipo de alojamiento que buscan los viajeros de negocios, que son los que más cancelan. En cuanto al segmento de mercado, las mayores cancelaciones se dan para las reservas realizadas por terceros (TA_TO). Llama la atención que el segmento grupos, sea el segundo mayor cancelador de reservas, dado que, en términos de importancia, era el tercero a nivel total. Puede que las anulaciones en las reservas se deban a cancelaciones en las reuniones de negocios de estos grupos por temas presupuestarios en las compañías?20.

Al analizar las tarifas medias que se pagan por tipo de cliente, vemos que los clientes individuales que cancelaron son los que tienen una tarifa más elevada: app USD 110 por noche, lo cual es app USD 6 más elevado que el grupo de viajeros individuales que terminó su reserva en términos planificados (USD 105). Fue el precio el determinante para la cancelación de la reserva?. Si analizamos el grupo de los clientes “Grupo” vemos que los canceladores y los que no se presentan tienen, también tarifas elevadas. El precio, creemos, puede ser un factor en la decisión de cancelación. Veremos con el desarrollo de algoritmos si la variable es significativa21.

Si analizamos las cancelaciones por mes, vemos que ellas siguen un patrón muy parecido a las reservas. Los meses en que más cancelaciones se producen son: Agosto, Julio y Mayo. Por día del mes, el panorama no es distinto, al igual que ocurría con las reservas, no hay un patrón claro que indique algún período del mes donde se producen más cancelaciones. Sin embargo, es posible ver la misma baja a mediados de mes que se observó en las reservas22. Dado que no conocemos cómo se obtuvieron los datos, no tenemos respuesta para este comportamiento.

5. Metodología a desarrollar en la práctica.

El objetivo de la práctica es el desarrollar un algoritmo de clasificación que permita predecir la conducta de un cliente frente a una reserva realizada en un hotel genérico23.

La variable a predecir tiene tres categorías, con lo que consideramos el problema como uno de clasificación multiclase. En particular, tres son las opciones que tiene un cliente que reservó una habitación en un hotel: realizar el check-in como estaba planificado, cancelar la reserva antes del día de check-in o no presentarse el día del check-in en el counter. Para el objetivo de predecir estas posibles tres alternativas, proponemos la creación de un algoritmo supervisado que considera el desarrollo de grupos de algoritmos usando la librería de Caret en R. Los algoritmos a cubrir son: Random Forest, Regresión Multinomial, Xgboost y Red neuronal.

Estos algoritmos fueron evaluados individualmente de manera de determinar sus mejores hiperparámetros. Una vez determinados y ajustados éstos, el mejor modelo evaluó su performance en el set de entrenamiento (train, 70% de las observaciones) con la métrica de Accuracy. Posibles sobreajustes fueron contrastados versus el set de validación (20% de las observaciones) con la misma métrica.

Para mantener consistencia, los 4 algoritmos fueron ajustados considerando una grilla de control de validación cruzada con 4 grupos. También se fijó la misma semilla para evitar distintos resultados debidos a cambios en las muestras.

Para la comparación entre algoritmos, se usó validación cruzada repetida y gráfico de cajas para Accuracy en base a mediana y su desviación estándar. Se usaron los mismos grupos, número de iteraciones y semilla24.

Paralelamente, se usó la librería H2O para el desarrollo de algoritmos, que no pudieron ser desarrollados en Caret, ya sea por complejidad o tamaño del set de datos: Gradient Boosting, Red Neuronal Profunda y ensamblado (stacking). Estos algoritmos se compararon versus la mejor alternativa definida por Caret.

Una vez determinado el mejor algoritmo para el set de datos se evaluó, finalmente, el performance de éste versus el set de datos test (10% de las observaciones).

Respecto del set de variables que se usó en los diferentes algoritmos, se utilizó un modelo Random Forest con el fin de reducir las variables a aquellas que más impacto tenían en el accuracy del modelo.

6. Análisis preliminar de datos, y primer EDA realizado en Excel.

Previa carga de datos a RStudio, se realizaron los siguientes cambios / mejoras a la base de datos: Revisar y eliminar los espacios en blanco en las categorías de las variables.

Realizar cambio en la notación decimal americana (comas para miles, puntos para decimales) por notación Latinoamericana: puntos para los decimales y comas para los miles.

Estandarizar notación para los valores perdidos: Para variables numéricas se recatalogan como “9999”, para variables categóricas, se recatalogan como “Unknown”.

Cambio en las nomenclaturas para facilitar posteriores análisis: Sustitución del código ISO 3155-3:2013 para los países, por su nombre; sustitución del código estándar de “meal hospitality” a su nombre correspondiente, y sustitución del mes de arribo (nombre), por su equivalente numérico.

Dado que la base cuenta con el campo “país” (alfanumérico), se agregaron 2 campos nuevos: Longitud y Latitud medias del respectivo país.

Respaldo de datos en archivo txt separado por tabulaciones, de esta forma evitamos problemas con la notación decimal Latinoamericana.

7. Tratamiento de datos en RStudio.

Una vez cargados los datos a RStudio se procedió al tratamiento de ellos25. Básicamente lo que se realizó a los datos fue lo siguiente:

Revisión y recategorización de variables mal importadas : En este punto, las siguientes variables numéricas pasaron a factor, dado su bajo número de categorías: “is_canceled”, “arrival_date_year”, “arrival_date_month”, “arrival_date_day_of_month”, “is_repeated_guest”, “agent” y “company”.

Las variables “arrival_date” y “reservation_status_date” pasaron a formato fecha.

Agrupamiento de categorías de variables : Se realizó un análisis de las frecuencias de las categorías en las diferentes variables para ver posibles alternativas de agrupamiento de aquellas categorías muy bajo representadas. De esta forma, se creó la categoría “Other” en las siguientes variables: “meal”, “market_segment”, “distribution_chanel”, “reserved_room_type”, “assigned_room_type” y “customer_type”.

Para el caso de la variable “arrival_date_month”, los meses de llegada de los viajeros, se agruparon por trimestres: “Q1”, “Q2”, “Q3” y “Q4”.

En la variable “arrival_date_day_of_month”, los días del mes en el que llegan viajeros se agruparon en mitades. En este sentido, “H1” para los días de la primera mitad del mes y “H2”: para los días de la segunda mitad del mes.

Valores perdidos : Se recatalogaron las variables marcadas como perdidas (NA): “Unknowmn” en variables categóricas, y “9999” en variables numéricas. Este cambio de nombre se realizó en aquellos casos en los que los valores perdidos no eran representativos para una variable en particular. En el caso de los valores “Unknown” en las variables “agent”, “country” y “company”, los valores quedaron con la misma etiqueta para reflejar que corresponde a valores desconocidos, pero que son importantes para el análisis.

Valores fuera de rango : Nos dimos cuenta que la variable “av_daily_rate” incluía valores negativos. Esto no era posible, dado que la variable representa el valor de la habitación por noche, por lo que ellos se renombraron como NA para posterior imputación.

Revisión de las categorías del set de datos: Nuevamente se realizó una revisión de los tipos de variables presentes en el set de datos. Para nosotros solo debería haber variables: factor, numéricas y fechas. Se corrigieron aquellas variables que no tenían alguna de estas 3 clases.

Tratamiento de valores atípicos (outliers) y perdidos (NA) : Se consideraron como valores atípicos, aquellas observaciones que desvíaban de la medía en más de 3 desviaciones estándar; y/o aquellas observaciones que se desviaban de la media, en más de 8 desviaciones absolutas. Aquellos casos se marcaron como NA, para una posterior imputación.

Tanto para el caso de los atípicos, como para los valores perdidos, se verificó que ellos no superaban el 50% de los datos en una variable respectiva. De esta forma era posible su imputación, sin perder información.

El criterio utilizado para imputar valores perdidos (NA) fue el siguiente: media para el caso de los valores perdidos en variables numéricas, y moda, en el caso de las variables categóricas.

Los valores tratados se guardaron en archivo RDS datosLimpios.

8. Feature Engineering.

Una vez limpio el set de datos, fue el turno de la realización de cambios en las variables previo desarrollo de cualquier algoritmo26.

En este apartado se hicieron los siguientes cambios:

Creación de variables: A partir del set de datos datosLimpios se crearon una serie de variables que buscaban aportar más información: fe_lonLat, que es una variable de distancia a partir de longitud y latitud de cada país. fe_fechaReserva, que determina la fecha en la que se realizó la reserva en el hotel. A partir de ésta y las variables fechas ya existentes en el set de datos, se crearon fe_diaSemanaReserva que muestra numéricamente el día de la semana en que se realizó la reserva (1 = Domingo, 2 = Lunes, etc) y fe_diaSemanaLlegada, que muestra lo mismo, pero para el día de llegada al hotel.

Por último, se creó la fe_totalFactura que estima el valor total de una estadía en el hotel respectivo por las noches contratadas.

Transformación de variables: Se realizó 2 tipos de transformación a variables existentes: uso de frecuencias: para el caso de variables factor con muchas categorías para agrupar, éstas se transformaron a variables numéricas, usando su frecuencia como la misma variable. Este procedimiento se realizó en las siguientes variables: agent, country y company.

aplicación de la función ln(): Esta alternativa se utilizó en el caso de variables numéricas donde existía un sesgo importante respecto de su media (ver anexos 17 y con histogramas de las variables numéricas antes y después de esta transformación). De esta forma, se trató de centrar nuevamente los datos. Las variables modificadas con esta técnica fueron las siguientes: Av_DailyRate, lead_time, stays_in_week_nights, stays_in_weekend_nights, total_of_special_requests y fe_totalFactura.

Eliminación de variables: Las variables transformadas por los 2 métodos descritos anteriormente, se eliminaron del set de datos, por cuanto su “transformada” tomaría su lugar. Adicionalmente se eliminaron variables que no aportaban información al set de datos: is_canceled, prop_missings, fe_fechaArrival, fe_fechaReserva, fe_fechaResStatus y variables con correlación alta entre ellas27: Lat_av y Long_av.

Estandarización y creación de variables dummies: Se procedió a crear variables dummies por cada variable factor. En el caso de las variables numéricas, se estandarizaron las variables que no sufrieron transformaciones vía función ln().

**Formación de los sets de datos para uso de algoritmos: Se crearon 2 sets de datos, compuestos, cada uno, de un set de entrenamiento con un 70% de los valores (83.574 datos), un set de validación con un 20% de las observaciones (23.880 datos), y finalmente un set de test con el 10% restante de los datos. (11.936 observaciones).

Set de datos 1 está compuesto por variables factor convertidas a dummies, más variables numéricas estandarizadas.

Set de datos 2 está compuesto por variables factor convertidas a dummies, más variables numéricas transformadas, más variables numéricas sin transformar estandarizadas. Este set de datos sería de uso eventual en caso de que el set 1 no produjera resultados en accuracy satisfactorios28.

9. Selección de variables.

Como fue mencionado en la sección de la metodología de la práctica, usamos un modelo Random Forest sobre el set de datos de entrenamiento (train), para determinar las variables más importantes, y de esta forma reducir la complejidad del set de datos para facilitar el performance de los algoritmos. Tenemos que recordar que Random Forest utiliza un método de selección de mejores variables al momento de crear los quiebres en las ramas de los árboles a construir.

El mismo algoritmo permite obtener la importancia de las variables por medio de la función modelo\(finalModel\)importance(). Esta función entrega la importancia de cada variable respecto de Accuracy y Gini global. Nosotros utilizamos el primer KPI. De esta forma, calculamos las 30 variables que tenían un impacto en la caída del Accuracy de un 90% acumulado. Dicho de otra forma, de eliminar estas 30 variables, el accuracy de nuestro modelo caería un 90%. Por lo tanto, son variables importantes29.

10. Algoritmos de la librería Caret.

10.1. Random Forest30.

En la práctica, se tunearon 5 algoritmos de Random Forest. Los modelos 1 a 4, sirvieron para la determinación de los mejores hiperparámetros de algoritmo, entrenándose sobre las 60 variables del set de train. El modelo 5, correspondió a la versión final y candidata a compararse con otros algoritmos.

Todos los modelos fueron entrenados con semilla 1234.

Random Forest 1 y 2: Estos modelos se corrieron con el objetivo de determinar un número de árboles óptimo. El modelo 1, consideró una grilla de: 1.000, 1.500, 2.000 y 2.500 arboles. Se fijó mtry como equivalente a sqt(# variables), y equivalente a 8. nodesize se fijó en 10.

El modelo 2 consideró una grilla de:500,2.500,3.000,3.500,4.000,5.000 árboles. En ambos modelos, la grilla de caret incluyó la opción sampling=“down” para manejar el problema del desbalanceo en la variable objetivo mencionado en el punto de análisis de datos31.

El accuracy de los modelos 1 y 2 no varía demasiado con el número de árboles32, fluctuando entre 70,3 y 70,4%. Para la práctica, consideraremos 2.000 árboles como un número apropiado.

Random Forest 3: Este modelo tuvo por finalidad el tuneo del hiperparámetro mtry. Se usó la opción de grilla de datos, incluyendo el vector c(3,4,5,6,7,8,9,10). El número de árboles se determinó del más apropiado de los modelos 1 y 2 e igual a 2.000. No se hicieron cambios en el resto de los parámetros. Caret sugirió que la opción de mtry=8 era la más apropiada.

Los accuracy obtenidos en los set de entrenamiento y validación fueron de un 72,6% y un 71,1% respectivamente33. Estos números no son muy espectaculares, por lo que se realizó un cuarto modelo.

Random Forest 4: Este modelo se tuneó con los hiperparámetros determinados de los modelos anteriores: ntree = 2.000, mtry = 8, nodesize = 10. El único cambio realizado fue la eliminación de la opción sampling=“downn” para ver el efecto final en el accuracy. Los resultados de este modelo se muestran a continuación:

Cuadro 1: Matriz de confusión modelo Random Forest 4 sobre datos entrenamiento:

Canceled Check_Out No_Show Total
Canceled 24.534 3.406 192 28.132
Check_Out 5.577 49.208 587 55.372
No_Show 1 3 66 70
Total 30.112 52.617 845 83.574
———– ———– ———– ———– ———–
Diagonal 73.808
Accuracy 88,3%


Cuadro 2: Matriz de confusión modelo Random Forest 4 sobre datos validación:

Canceled Check_Out No_Show Total
Canceled 7.106 1.498 - 8.604
Check_Out 967 14.067 - 15.034
No_Show 58 160 24 242
Total 8.131 15.725 24 23.880
———– ———– ———– ———– ———–
Diagonal 21.197
Accuracy 88,8%

Es posible ver que al quitar la opción downsample en caret hizo mejorar el accuracy del modelo de manera importante, dando app 17 puntos más en el KPI en los datos de validación (pasamos de 70,4% a 88,8%). El haber seleccionado downsample redujo de manea relevante el set de datos de validación, desde los 83K registros a los 2,5K registros, siendo una de las razones para resultados tan pobres del modelo 3.

La diferencia entre el accuracy del set de train y validación es app 0,2%, lo que es indicativo que no existe sobreajuste en los datos.

Una vez determinado que incluir downsample al modelo no mejoraba los resultados de accuracy, se usó el modelo 4, para reducir el número de variables 34. Esto dio origen al modelo 5.

Random Forest 5: Este modelo consideró los hiperparámetros ntrees=2.000, nodesize=10, mtry=635, y fue entrenado sobre un set de las 30 variables más importantes.

Los resultados del modelo fueron los siguientes:

Cuadro 3: Matriz de confusión modelo Random Forest 5 sobre datos train:

Canceled Check_Out No_Show Total
Canceled 24.634 3.693 202 28.529
Check_Out 5.477 48.924 569 54.970
No_Show 1 0 74 75
Total 30.112 52.617 845 83.574
Diagonal 73.632
Accuracy 88,1%


Cuadro 4: Matriz de confusión modelo Random Forest 5 sobre datos validación:

Canceled Check_Out No_Show Total
Canceled 7.132 1.472 0 8.604
Check_Out 1.042 13.992 0 15.034
No_Show 63 153 26 242
Total 8.237 15.617 26 23.880
———– ———– ———– ———– ———–
Diagonal 21.150
Accuracy 88,6%

La reducción de variables no afectó el accuracy del modelo, el cual se mantuvo en un 88,1% y 88,6% para los sets de entrenamiento y validación respectivamente. Tampoco hay evidencia de sobreajuste.

10.2. Modelo Multinomial36.

Se construyó un segundo modelo: multinomial como una forma de definir un modelo sencillo para el set de datos. De esta forma si el modelo era competitivo, se elegiría éste por sobre modelos más complejos; sobre todo, por el tema de interpretabilidad de los parámetros.

El modelo tuneado se entrenó sobre el set de 30 mejores variables definidas por Random Forest. El mejor modelo se obtuvo con un deacay = 0. La matriz de confusión para el modelo sobre el set de datos train es la siguiente:

Cuadro 5: Matriz de confusión Modelo multinomial sobre set de entrenamiento:

Canceled Check_Out No_Show Total
Canceled 60.812 14.902 587 76.301
Check_Out 29.524 142.949 1.948 174.421
No_Show - - - -
Total 90.336 157.851 2.535 250.722
———– ———– ———– ———– ———–
Diagonal 203.761
Accuracy 81,3%

De la tabla anterior, podemos ver que el mejor modelo muestra un accuracy de un 81,3%, que no es un mal resultado, pero es más bajo que el obtenido con Random Forest.

Se generaron las predicciones para el set de validación, y se calculó su matriz de confusión, la que es la siguiente:

Cuadro 6: Matriz de confusión Modelo multinomial sobre set de validación:

Canceled Check_Out No_Show Total
Canceled 5.794 1.428 59 7.281
Check_Out 2.808 13.606 183 16.597
No_Show 2 0 0 2
Total 8.604 15.034 242 23.880
———– ———– ———– ———– ———–
Diagonal 19.400
Accuracy 81,2%

Vemos que el modelo multinomial es estable, con un accuracy de un 81,2%, muy cercano al obtenido con el set de entrenamiento.

El próximo paso para este modelo es pasar a la prueba de validación cruzada repetida con mismos parámetros definidos para los modelos Caret.

10.3. Modelo Xgboost37.

Para este algoritmo, se tunearon 2 modelos: xgboost1: con el objetivo de determinar la mejor combinación de hiperperámetros. En este caso particular, se definió una grilla con las siguientes opciones: min_child_weight=c(5,10,15,20), eta=c(0.1,0.05,0.03,0.01,0.001), nrounds=c(100,500,1000,5000).

De la gráfica de este primer entrenamiento, pudimos concluir que un mayor número de iteraciones (nrounds) dominaba en todos los escenarios de accuracy, con tasas de shirnkage y número de nodos bajos38.

Un segundo modelo, xgboost2, se entrenó con los parámetros ganadores del modelo 1: eta = 0.05 y min_child_weight=5. Se dejó variable el número de árboles, para estudiar el accuracy en el rango entre 1.000 y 5.000 iteraciones39.

El máximo accuracy se produjo con 3.000 iteraciones.

El accuracy de este modelo óptimo40 es de un 88,2%. Es mejor que el logístico, y muy parecido al resultado de Random Forest.

Cuadro 7: Matriz de confusión Modelo Xgboost sobre set de entrenamiento:

Canceled Check_Out No_Show Total
Canceled 25.056 4.066 200 29.322
Check_Out 5.021 48.511 479 54.011
No_Show 35 40 166 241
Total 30.112 52.617 845 83.574
———– ———– ———– ———– ———–
Diagonal 73.733
Accuracy 88,2%

Se calcularon las predicciones para el set de validación, las que arrojaron un accuracy de un 88,9%; lo que es un buen resultado, estable y sin signos de sobreajuste.


Cuadro 8: Matriz de confusión Modelo Xgboost sobre set de validación:

Canceled Check_Out No_Show Total
Canceled 7.252 1.338 14 8.604
Check_Out 1.101 13.928 5 15.034
No_Show 57 133 52 242
Total 8.410 15.399 71 23.880
———– ———– ———– ———– ———–
Diagonal 21.232
Accuracy 88,9%

Próximo paso para el modelo es evaluar su desempeño en validación cruzada repetida con una semilla distinta.

10.4. Modelo Red Neuronal41.

Se realizaron 2 versiones del algoritmo: red1 con método nnet y red2 con método avNNet. Para ambos, se revisaron los siguientes hiperparámetros: size=c(10,15,20)42 y decay=c(0.1,0.01,0.001)43:

En ambos modelos, el más alto accuracy fue obtenido con 20 nodos en su capa intermedia. De esta forma, la arquitectura de la red fue de 30x20x344.

Respecto del decay óptimo para red1 fue de 0,001 y para red2 fue de 0.1.

El modelo de red neuronal seleccionado para el proceso de validación cruzada repetida (VCR) fue el modelo 1, que presentó un accuracy de un 86,3%, de acuerdo con el siguiente cuadro:

Cuadro 9: Matriz de confusión Modelo Red Neuronal nnet sobre set de entrenamiento:

Canceled Check_Out No_Show Total
Canceled 24.199 4.665 239 29.103
Check_Out 5.909 47.948 588 54.445
No_Show 4 4 18 26
Total 30.112 52.617 845 83.574
———– ———– ———– ———– ———–
Diagonal 72.165
Accuracy 86,3%

Al realizar proyecciones sobre el set de validación, nnet, mostró un accuracy de un 86,6%. Su matriz de confusión es la siguiente:


Cuadro 10: Matriz de confusión Modelo Red Neuronal nnet sobre set de validación:

Canceled Check_Out No_Show Total
Canceled 6.959 1.645 0 8.604
Check_Out 1.305 13.729 0 15.034
No_Show 71 169 2 242
Total 8.335 15.543 2 23.880
———– ———– ———– ———– ———–
Diagonal 20.690
Accuracy 86,6%

Respecto de la red avNNET, presentó accuracy en entrenamiento y validación de 86,9% y 86,8% respectivamente45. Sin embargo, al probar su desempeño en el proceso de VCR, el requerimiento computacional fue demasiado. El algoritmo estuvo aprendiendo por más de 24 horas en un laptop normal46, y más de 10 horas en Google Compute Engine47 sin generar resultados en ambos casos.

10.5. Validación cruzada repetida (VCR) de los modelos de Caret48.

El proceso de validación cruzada repetida se realizó para los 4 modelos tuneados de Caret. Estos modelos se enfrentaron a los mismos parámetros a saber: 10 grupos, 10 repeticiones, semilla=4321 y tamaño de la muestra = 2.00049.

Los 4 modelos se compararon en base a la métrica de Accuracy, considerando su mediana y su desviación estándar.

Los resultados del proceso VCR, pueden verse en la siguiente gráfica50:

Comparación modelos Caret problema de clasificación Reservas Hotel

Comparación modelos Caret problema de clasificación Reservas Hotel

Podemos concluir que los cuatro algoritmos son buenos modelos, en todos se tiene un accuracy sobre el 85% y con una muy baja desviación estándar en las iteraciones. En esta dimensión, el algoritmo más estable es el multinomial, con una desviación estándar de solo 0.0001, seguido por Random Forest con una desviación similar de 0.0002, pero con 4 puntos más de accuracy (85,8%), lo que lo hace preferido respecto del modelo multinomial.

El algoritmo que más destaca es Xgboost, quien presenta una amplia ventaja respecto del resto de modelos con un accuracy de un 88,4% 51, aun cuando es el algoritmo que posee la más alta desviación, este alto valor de accuracy compensa este último hecho.

Xgboost será el algoritmo elegido en esta parte de la práctica para la librería Caret. El próximo paso es compararlo con el mejor modelo de H2O.

11. Algoritmos de la librería H2O52.

Para completar la práctica quisimos verificar si era posible generar un modelo competitivo al Xgboost de Caret, con la librería H2O en R. Nuestro trabajo se realizó por medio de la interfaz web “Flow” de H2O53, que permitió la generación y entrenamiento de modelos sin necesidad de ingreso de código; y de manera más visual54.

Los modelos que probamos para la base de datos fueron: Gradient Boosting55, Red Neuronal profunda, y a partir de ellos, construimos modelo ensamblado (stacking) con diferentes alternativas de metalearners.

A diferencia que en el apartado de Caret, los modelos una vez generada su mejor versión, pasaron de manera directa a validación cruzada repetida, con los mismos 10 grupos y la misma semilla utilizada en Caret, de manera de mantener consistencia en la comparación.

11.1. Modelo Gradient Boosting(GBM).

En Flow probamos varios modelos que nos permitieron determinar buenas combinaciones de hiperparámetros. Particularmente, desarrollamos un modelo con la siguiente configuración56:

Hiperparámetros modelo GBM H2O

Hiperparámetros modelo GBM H2O

En H2O incluimos la opción de early stopping en la configuración del modelo; de manera de evitar el sobreajuste. Se seleccionó la opción de métrica AUTO, para una desviación de 0.001. De esta forma, seleccionamos un alto número de árboles (5.000). Early stopping detuvo las iteraciones a los 375 árboles.

Los resultados obtenidos sobre el set de datos sobre el set de entrenamiento fueron los siguientes:

Cuadro 11: Matriz de confusión Modelo GBM H2O sobre set de entrenamiento:

Canceled Check_Out No_Show Total
Canceled 29.892 214 6 30.112
Check_Out 130 52.486 1 52.617
No_Show 8 37 800 845
Total 30.030 52.737 807 83.574
———– ———– ———– ———– ———–
Diagonal 83.178
Accuracy 99,5%


Cuadro 12: Matriz de confusión Modelo GBM H2O sobre set de validación:

Canceled Check_Out No_Show Total
Canceled 7.296 1.303 5 8.604
Check_Out 1.043 13.986 5 15.034
No_Show 55 134 53 242
Total 8.394 15.423 63 23.880
———– ———– ———– ———– ———–
Diagonal 21.335
Accuracy 89,3%

Podemos ver que el modelo entrega un accuracy de un 99,5% y un 89,3% para los sets de entrenamiento y validación respectivamente. Estos números podrían sugerir sobreajuste, sin embargo, al revisar la gráficas de las curvas de error, no se evidencia este hecho, gracias a early stopping57:

11.2. Red Neuronal Profunda.

Dados los resultados obtenidos por nnet en Caret, siendo el segundo mejor modelo, nos preguntamos si era posible, aumentando la complejidad de una red, obtener mejores accuracy. De esta forma construimos 4 modelos usando la opción Deep Learning de la librería H2O.

Se desarrollaron 4 modelos con distintas estructuras de red58: deep1: con estructura [30,200,200,3], deep2: con estructura [30,512,3], deep3: con estructura [30,64,64,64,3] y por último deep4: con estructura [30,32,32,32,32,32,3].

En el proceso de desarrollo, no se incluyó rectificación, ni dropout; opciones que se utilizarían en caso de obtener valores de accuracy bajos.

Los 4 modelos fueron construidos con funciones de pérdida cross entropy, función de activación tanh, learning rate de 0.0001 y función multinomial. Se incluyó early stopping con función longloss, criterio 0.0001 y un número de iteraciones (epochs) de 20.

Para ahorrar tiempo de proceso, se usó la opción de scoring para entrenamiento y validación: de esta forma, estos procesos se realizaron con muestras de train de 10.000 datos (80%) y de validación de 2.500 datos (20%); en lugar que con la totalidad del set de datos.

Todos los modelos incluyeron la opción de validación cruzada repetida con 10 grupos y misma semilla que la usada en Caret.

Los mejores valores de accuracy se obtuvieron con las configuraciones de deep2 y deep459, que fueron las siguientes:

Los resultados obtenidos por la red 2 en train y validación fueron: 79,9% y 79,6% respectivamente. Estos resultados no son especialmente espectaculares; sin embargo, nuestro objetivo es usar la red en un modelo stacked, no de forma individual.

Cuadro 13: Matriz de confusión Modelo Deep Learning 2 H2O sobre set de entrenamiento:

Canceled Check_Out No_Show Total
Canceled 1.790 1.694 0 3.484
Check_Out 203 6.146 0 6.349
No_Show 11 88 0 99
Total 2.004 7.928 0 9.932
———– ———– ———– ———– ———–
Diagonal 7.936
Accuracy 79,9%


Cuadro 14: Matriz de confusión Modelo Deep Learning 2 H2O sobre set de validación:

Canceled Check_Out No_Show Total
Canceled 489 452 0 941
Check_Out 47 1.531 0 1.578
No_Show 2 18 0 20
Total 538 2.001 0 2.539
———– ———– ———– ———– ———–
Diagonal 2.020
Accuracy 79,6%

Una de las características de esta red con alto número de neuronas en solo 1 capa es su alta variabilidad en los datos, lo que puede ser apreciado su gráfica de errores60.

Para el caso de la red4, los resultados de accuracy fueron:

Cuadro 15: Matriz de confusión Modelo Deep Learning 4 H2O sobre set de train:

Canceled Check_Out No_Show Total
Canceled 2.484 1.000 0 3.484
Check_Out 504 5.845 0 6.349
No_Show 23 76 0 99
Total 3.011 6.921 0 9.932
———– ———– ———– ———– ———–
Diagonal 8.329
Accuracy 83,9%


Cuadro 16: Matriz de confusión Modelo Deep Learning 4 H2O sobre set de validación:

Canceled Check_Out No_Show Total
Canceled 663 278 0 941
Check_Out 133 1.445 0 1.578
No_Show 5 15 0 20
Total 801 1.738 0 2.539
———– ———– ———– ———– ———–
Diagonal 2.108
Accuracy 83,0%

Para la red deep4, los valores de accuracy para entrenamiento y validación fueron de 83,9% y 83%. Son valores mejores que los obtenidos con la red 2, pero inferiores a los que se podía observar con Caret. Podemos ver que para los datos, es mejor una red con más capas ocultas y menos neuronas, que la opción inversa: más neuronas y menos capas ocultas61.

Con Deep Learning, no fue posible armar un modelo que generara más de un 85% de accuracy. No obstante, los resultados obtenidos, los accuracy generados en el proceso VCR fueron: 82,8% y 84,4% para las configuraciones de redes 2 y 4 respectivamente.

11.3. Ensamblado (stacking) en H2O.

Como otra de las ventajas del uso de H2O, estuvo el hecho de poder “ensamblar” modelos usando algoritmos preentrenados como variables independientes para un “metalearner”. Para nuestro set de datos, utilizamos los algoritmos Gradient Boosting, Deep learning 2 y Deep learning 4 con los siguientes metalearners: GLM, Random Forest y Gradient Boosting. En todos los casos se usó validación cruzada repetida con 10 grupos y misma semilla que la usada en Caret.

La idea detrás de ensamblar un modelo fue aprovechar la alta varianza de los modelos de red para obtener valores de accuracy más elevados, pero con la estabilidad del modelo Gradient Boosting. Las gráficas de las funciones de error, muestran que los picos y valles que se podían ver en los modelos de red, se han suavizado62.

11.4. Validación cruzada repetida (VCR) de los modelos de H2O.

Como se realizó con Caret, se tomaron los valores de accuracy de cada modelo para cada grupo de VCR, los datos se ordenaron y graficaron usando boxplot() para apreciar qué modelo lo hace mejor.

La gráfica que compara los modelos de H2O es la siguiente63:

Hiperparámetros modelo GBM H2O

Hiperparámetros modelo GBM H2O

De la gráfica, podemos ver claramente que los modelos de red presentan una más alta varianza y una mediana en accuracy más baja. El modelo Gradient Boosting, como se esperaba, muestra un muy buen accuracy y estable. Sin embargo, los 3 modelos ensamblados se desempeñan mejor que este último, siendo el stacked con metalearner GLM el que genera el accuracy más alto y cercano al 90%. Este modelo se selecciona como finalista de la librería H2O.

12. Comparación Caret vs H2O.

El modelo stacked GLM, comparado con Xgboost de Caret puede verse en el siguiente gráfico de cajas sobre sus VCR.

Comparación Xgboost Caret vs mejor Stacked GLM H2O

Comparación Xgboost Caret vs mejor Stacked GLM H2O

Al realizar una comparación con el modelo Xgoost entrenado con Caret, podemos ver que el modelo ensamblado se desempeña mejor que éste, con una diferencia de casi un punto en sus medianas64. A pesar de presentar una varianza mas elevada, el valor mínimo observado es superior al valor máximo que es posible alcanzar con Xgboost. Este hándicap es que buscamos y el que nos hace decidir por este modelo como el mejor para nuestra serie de datos.

13. Predicciones sobre el set de test y evaluación final.

Como último paso en nuestra práctica, para los 2 modelos finalistas, se realizó carga del set de datos de test, para evaluar el performance final del modelo.

Para el caso del modelo Xgboost de Caret, las predicciones sobre test entregaron un accuracy de un 88,6%. La siguiente es su matriz de confusión:

Cuadro 17: Matriz de confusión Modelo Xgboost de Caret sobre set de test:

Canceled Check_Out No_Show Total
Canceled 3.566 724 11 4.301
Check_Out 528 6.982 5 7.515
No_Show 26 66 28 120
Total 4.120 7.772 44 11.936
———– ———– ———– ———– ———–
Diagonal 10.576
Accuracy 88,6%

Para el caso del modelo stacking 2 con metalearner GLM de H2O, las predicciones sobre el set de test entregan un accuracy de un 89,1%. La siguiente es su matriz de confusión:


Cuadro 18: Matriz de confusión Modelo stacking2 con metalearner GLM de H2O, sobre set de test:

Canceled Check_Out No_Show Total
Canceled 3.581 714 6 4.301
Check_Out 478 7.029 8 7.515
No_Show 24 71 25 120
Total 4.083 7.814 39 11.936
———– ———– ———– ———– ———–
Diagonal 10.635
Accuracy 89,1%

Aún, cuando la diferencia no es importante, Como se esperaba, el modelo stacked GLM mostró un desempeño que es superior al modelo Xgboost. De esta forma, es el algoritmo declarado como el mejor modelo para nuestro set de datos.

14. Conclusiones.

Como conclusiones a la práctica puedo mencionar:

  • La base de datos utilizada correspondió a una base disponible en un repositorio público (Kaggle.com). Este hecho tuvo como principal implicancia el bajo control en el proceso de obtención de los datos. Pudimos observar ciertos sesgos en la información de la base, asociada por ejemplo al tipo de cliente, que no pudimos explicar dada la nula información adicional con que contamos. Adicionalmente nuestra investigación no se pudo orientar a un objetivo concreto, más que exploración y análisis general, por el mismo motivo; es decir, no conocer la fuente de obtención de datos y los objetivos concretos de su colección.

  • El mejor algoritmo para predecir la tasa de cancelación en reservas de hotel para nuestra base de datos, es un modelo ensamblado formado por un modelo Gradient Boosting y 2 redes neuronales con configuración [30x512x3] y [30x32x32x32x32x32x3]. Este modelo usa un metalearner de GLM, y explica correctamente un 89,1% de los datos del set de test, mostrando estabilidad en su desempeño.

  • No obstante el punto anterior, creemos que el modelo puede volver a la etapa de diseño para probar algunos cambios en parámetros, no probados en esta instancia, de manera de ver posibles mejoras marginales en el accuracy observado. Particularmente parámetros que no se tocaron en las redes fueron las tasas de dropout en capas input y ocultas, y los parámetros de rectificación.

  • Un segundo buen modelo para ser usado en nuestra base es un modelo Xgboost de la librería Caret. Este modelo es capaz de predecir correctamente un 88,6% de los datos del set de test.

  • Pudimos comprobar las bondades de los modelos ensamblados (stacking). Todos mos modelos ensamblados que se usaron en la base (mezcla de Gradient Boosting y Redes Neuronales con distintos metalearners), presentaron un mejor desempeño, en accuracy, que sus modelos individuales en la librería H2O.

  • Validamos Random Forest como un muy buen modelo para ser usado en etapas iniciales de construcción de algoritmos, en la medida que permite reducir variables manteniendo el poder de predictibilidad con las variables seleccionadas. En nuestro caso, pudimos eliminar la mitad de las variables del set de datos, pasando de 60 a 30 variables, simplificando los posteriores modelos.

  • Dado el desbalanceo presente en la variable objetivo, probamos con usar remuestreo con “downsample” (categorías más representadas bajan su número respecto de las menos representadas en el proceso de muestreo) para tratar de corregir el problema y no afectar las predicciones de nuestros modelos. Este proceso se llevó a cabo en Random Forest, con resultados pobres en accuracy; es decir, no se mejoraron las predicciones en el modelo construido (accuracy bajo 80%) versus los modelos sin esta opción de corrección. Para futuros estudios recomendamos probar con paquete Rose en Caret, para evaluar su desempeño.

15. Referencias.

  • Base de datos Hotel Booking Demand, Jesse Mostipak, disponible en kaggle (https://www.kaggle.com/jessemostipak/hotel-booking-demand). Esta base fue obtenida primariamente desde Hotel Booking Demand Datasets de Nuno Antonio, Ana Almeida, and Luis Nunes for Data in Brief, Volume 22, February 2019 (https://www.sciencedirect.com/science/article/pii/S2352340918315191).

  • Deep Learning, Quick Referene; Mike Bermico. 2018.

  • Machine Learning Using R, Karthik Ramasubramanian y Abhishek Singh. 2nd Edition, 2019.

  • Códigos utilizados fueron extraídos en su mayoría de apuntes de los módulos minería de datos y machine learning.

16. Anexos.

Anexo 1: País de origen del viajero representado en la base de datos.

País de origen de quien busca habitación en un hotel

País de origen de quien busca habitación en un hotel

No tenemos más información del viajero representado en los datos. Sin embargo, es posible apreciar que en su mayoría es europeo. Llama la atención la muy baja representación de personas de Asia y América. Los principales países de origen son Portugal, UK, Francia, España y Alemania.

Anexo 2: Dónde y cómo aloja el cliente representado en los datos.

Dónde y cómo aloja el cliente tipo de la base de datos.

Dónde y cómo aloja el cliente tipo de la base de datos.

Cliente mayoritariamente de negocios, que busca estadías cortas en hoteles con comodidades básicas.

Tipo de hotel en el que hace las reservas el cliente tipo de la base de datos.

Tipo de hotel en el que hace las reservas el cliente tipo de la base de datos.

El viajero que muestran los datos mayoritariamente se aloja en hoteles de ciudad. Otro argumento más que soporta la tesis de un viajero mayoritariamente de negocios.

Estadísticas promedio del viajero de negocios representado en la base de datos.

Estadísticas promedio del viajero de negocios representado en la base de datos.

Los datos sugieren un viajero de negocios -viaja solo-, mayoritariamente aloja en estadías cortas -menos de 3 noches en días de semana- y en habitaciones básicas, sin demasiados servicios adicionales; y en hoteles de ciudad. Su gasto medio por noche es de app USD 100.

Anexo 3: El viajero de la base de datos desde el punto de vista del negocio hotelero.

El viajero de los datos es frecuente ?. (0=No, 1=Si)

El viajero de los datos es frecuente ?. (0=No, 1=Si)

Los datos muestran que el viajero es nuevo para las cadenas de hoteles. Esto porque en su gran mayoría (97%) no es frecuente.

Historial de cancelaciones del viajero de negocios de la base de datos.

Historial de cancelaciones del viajero de negocios de la base de datos.

Adicionalmente, la persona que reserva noches de hotel no tiene un historial de cancelaciones previas, lo cual es consistente con el gráfico anterior respecto de que la persona no es un viajero frecuente.

Segmento de mercado - tipo de pago por la reserva de habitación.

Segmento de mercado - tipo de pago por la reserva de habitación.

Desde el punto de vista del negocio hotelero, el viajero medio de la base de datos es un pasajero nuevo, sin historial de reservas ni cancelaciones previas. Realiza sus reservas por medio de terceros (Agencias de viaje u operadores turísticos) y por lo general, realiza algún pago para garantizar su reserva.

Anexo 4: Información de check-in de los viajeros de la base de datos.

Perfil de Check inn de los viajeros en la base de datos.

Perfil de Check inn de los viajeros en la base de datos.

Por lo general, los mayores ingresos se producen en meses de verano del hemisferio norte -Agosto y Julio-. Mayo también es un mes con altos ingresos de reservas. Respecto del día del mes, no hay un patrón claro. Sin embargo, se ve una reducción en los ingresos de reservas a mediados de mes. No tenemos mayor información para explicar este hecho.

Anexo 5: Situación de las reservas de hotel por año.

Apertura de reservas por año y status de la misma.

Apertura de reservas por año y status de la misma.

Datos muestran un crecimiento importante en las reservas realizadas en periodo 2016/2015 (+157%), mientras que una caída de un 29% entre 2017/2016. No tenemos información para explicar este movimiento. Las cancelaciones, por el contrario, se muestran estables en torno al 35% del total de reservas.

Anexo 6: Estadía media de viajeros, por tipo de hotel.

Estadía media de las personas que reservan habitaciones en la base de datos.

Estadía media de las personas que reservan habitaciones en la base de datos.

Hay una clara diferencia entre estadías de acuerdo al tipo de hotel. Los resorts, en promedio tienen una estancia media de +1día respecto de los hoteles de ciudad. El cliente que más se hospeda, es el cliente con tarifa corporativa, quedándose aproximadamente 6 días en resorts. Es este alojamiento canje de puntos?

Anexo 7: Variable objetivo del set de datos.

Detalle del status de las reservas - Variable Objetivo.

Detalle del status de las reservas - Variable Objetivo.

Problema de clasificación multiclase y claro desbalanceo en la variable objetivo. Clase mayoritaria -Check_out- concentra el 63% de las observaciones. Las cancelaciones de reservas representan el 36% de los datos. En la práctica, veremos alternativa de realizar proceso de “downsample” como opción en la grilla de control de Caret para tratar de corregir el problema.

Anexo 8: Cancelaciones de reservas por tipo de cliente y por año.

Cancelaciones por tipo de cliente y año.

Cancelaciones por tipo de cliente y año.

Existe algo con los datos de 2015?. Hay algún hecho que explique el salto entre 2015/2016?. Con los datos que tenemos, no podemos dar respuestas. Las cancelaciones son consistentes con el tipo de viajero en la base: mayoritariamente de negocios.

Anexo 9: Cancelaciones de reservas por tipo de cliente y país de origen.

Cancelaciones por tipo de cliente y país.

Cancelaciones por tipo de cliente y país.

Las cancelaciones por país también son consistentes con la distribución de los países en la muestra, con la excepción de Italia, que en el ranking de países aparece más abajo del lugar 10, mientras que en cancelaciones es top 5. Son más exigentes los Italianos ?, son más sensibles al precio?.

Anexo 10: Cancelaciones por tipo de Hotel y segmento de mercado.

Cancelaciones por tipo de hotel y segmento de mercado.

Cancelaciones por tipo de hotel y segmento de mercado.



Anexo 11: Tarifas meda habitación/noche, por tipo de reserva y tipo de cliente.

Tarifas medias por cliente y status de reserva.

Tarifas medias por cliente y status de reserva.

Los datos muestran que los clientes que más cancelaron reservas de hotel son los viajeros de estadías cortas que viajan solos. Estos clientes enfrentaron tarifas de app +USD 30 que los clientes que tienen tarifas corporativas, y app +USD 18 que los grupos de viajeros. Dentro del grupo de viajeros solos, la diferencia de tarifa entre quienes cancelaron y los que no es de app +USD 6. Es el precio un determinante en la cancelación de reservas?.

Anexo 12: Perfil de cancelaciones de reserva por mes y día.

Perfil de cancelaciones por mes y día del mes.

Perfil de cancelaciones por mes y día del mes.

Como indicamos las gráficas de cancelaciones son muy similares a las que muestran las gráficas de check-in; es decir, altas cancelaciones en meses de verano en hemisferio norte -Agosto y Julio-, y adicionalmente, Mayo. Respecto del día, no hay patrón claro, sin embargo, se ve la misma baja en cancelaciones a mediados de mes.


Anexo 13: Código R para depuración del set de datos de kaggle.com.

#=========================================================================
# PREPARACION PRELIMINAR
#=========================================================================
# Lectura del set de datos previamente trabajado en Excel:
datos<-read.csv2("hotel_bookings.csv")

# Cargamos conjunto de funciones y librerías necesarias: 
source("Funciones_R.R")
library(dummies)
library(questionr)
library(psych)
library(car)
library(corrplot)
library(caret)
library(lubridate)
library(data.table)

#=========================================================================
# DEPURACION DE DATOS
#=========================================================================
# Revisamos variables mal categorizadas y corregimos:

# 1) Revisamos las variables numéricas para ver aquellas que deberían ser factor:
sapply(Filter(is.numeric, datos),function(x) length(unique(x)))

# Variables que deben pasar a factor:"is_canceled", "arrival_date_year",
# "arrival_date_month", "arrival_date_day_of_month" , "is_repeated_guest",
# "agent", "company":

datos[,c(2,4,5,6,7,20,27,28)] <- lapply(datos[,c(2,4,5,6,7,20,27,28)], factor)

# 2) Las variables arrival_date & reservation_status_date las pasamos a formato fecha:

datos$fe_fechaArrival <- dmy(datos$arrival_date)
datos$fe_fechaResStatus <- dmy(datos$reservation_status_date)

# Vemos las variables factor si hay categorías poco representadas:
freq(datos$hotel,sort = "dec")
freq(datos$is_canceled,sort = "dec")
freq(datos$arrival_date_year,sort = "dec")
freq(datos$arrival_date_month,sort = "dec")
freq(datos$arrival_date_day_of_month,sort = "dec")
freq(datos$meal,sort = "dec")
freq(datos$country,sort = "dec")
freq(datos$market_segment,sort = "dec")
freq(datos$distribution_channel,sort = "dec")
freq(datos$is_repeated_guest,sort = "dec")
freq(datos$reserved_room_type,sort = "dec")
freq(datos$assigned_room_type,sort = "dec")
freq(datos$deposit_type,sort = "dec")
freq(datos$agent,sort = "dec")
freq(datos$company,sort = "dec")
freq(datos$reservation_status,sort = "dec")
freq(datos$customer_type,sort = "dec")

# Agrupamos categorías bajo representadas:
datos$meal<-car::recode(datos$meal,"'No_Meal'='Other';
                                  'Full_Board'='Other'
                                  ")
datos$market_segment<-car::recode(datos$market_segment,"'Direct'='Other';
                                  'Corporate'='Other';
                                  'Complementary'='Other';
                                  'Aviation'='Other'
                                  ")
datos$distribution_channel<-car::recode(datos$distribution_channel,"'Corporate'='Other';
                                  'Global_DistributionServ'='Other'
                                  ")
datos$reserved_room_type<-car::recode(datos$reserved_room_type,"'F'='Other';
                                  'G'='Other';
                                  'B'='Other';
                                  'C'='Other';
                                  'H'='Other';
                                  'P'='Other';
                                  'L'='Other'
                                  ")
datos$assigned_room_type<-car::recode(datos$assigned_room_type,"'B'='Other';
                                  'H'='Other';
                                  'I'='Other';
                                  'K'='Other';
                                  'P'='Other';
                                  'L'='Other'
                                  ")
datos$deposit_type<-car::recode(datos$deposit_type,"'Non_Refund'='Deposit';
                                  'Refundable'='Deposit'
                                ")
datos$arrival_date_month<-car::recode(datos$arrival_date_month,"c('1','2','3')='Q1';
                                  c('4','5','6')='Q2';
                                  c('7','8','9')='Q3';
                                  c('10','11','12')='Q4';
                                  ")
datos$arrival_date_day_of_month<-car::recode(datos$arrival_date_day_of_month,"
                                  c('1','2','3','4','5','6','7','8',
                                  '9','10','11','12','13','14','15')='H1';
                                  c('16','17','18','19','20','21','22','23',
                                  '24','25','26','27','28','29','30','31')='H2'
                                  ")
datos$customer_type<-car::recode(datos$customer_type,"'Contract'='Other';
                                  'Group'='Other'
                                ")
datos$company<-car::recode(datos$company,"'9999'='Unknown';
                          ")
datos$agent<-car::recode(datos$agent,"'9999'='Unknown';
                          ")

# Recatalogamos valores "Unknown" en variables factor
# con baja representación como missing (NA):
datos$meal<-recode.na(datos$meal,"Unknown")
datos$market_segment<-recode.na(datos$market_segment,"Unknown")
datos$distribution_channel<-recode.na(datos$distribution_channel,"Unknown")

# Recatalogamos valores "9999" en variables numéricas como missing (NA):
datos$meal<-recode.na(datos$meal,"9999")
datos$children<-recode.na(datos$children,"9999")
datos$Long_av<-recode.na(datos$Long_av,"9999")
datos$Lat_av<-recode.na(datos$Lat_av,"9999")

# Valores fuera de rango, en average daily rate hay valores negativos, esto no puede ser
# ellos pasan a NA's:
datos$Av_DailyRate<-replace(datos$Av_DailyRate, which((datos$Av_DailyRate < 0)), NA)

# Cambiamos a numeric todas las variables que no son factor
datos[,c(3,8:13,16,17,21:22,25,29,31:33)<-lapply(datos[,
                                                 c(3,8:13,16,17,21:22,25,29,31:33)],
                                                 as.numeric)

# Separamos el set de datos para poder imputar missings & outliers
varObj<-datos$reservation_status
input<-as.data.frame(datos[,-34])
noImputar<-input[,c(11,12,13,21,22,25,29,32,33)]
imputar<-input[,-c(11,12,13,21,22,25,29,32,33)]

#=========================================================================
# Tratamiento de missings & outliers
#=========================================================================
# Revisamos la cantidad de outlyers por variable:
sapply(Filter(is.numeric, imputar),function(x) atipicosAmissing(x)[[2]])/nrow(imputar)

# El % de atipicos por variables numéricas está bajo del 1%. Por lo tanto, recatalogamos
# a missings para luego imputar:

imputar[,as.vector(which(sapply(imputar, class)=="numeric"))]<-sapply(
  Filter(is.numeric, imputar),function(x) atipicosAmissing(x)[[1]])

# Revisamos la cantidad de missings por variable:
imputar$prop_missings<-apply(is.na(imputar),1,mean) # Por observación
summary(imputar$prop_missings) # Max % es un 14%, es aceptable para imputar
(prop_missingsVars<-apply(is.na(imputar),2,mean)) # Por variable

# Verificamos que el % de missings por variable es bajo, por lo tanto, imputamos:

# Imputo todas las cuantitativas por mediana
imputar[,as.vector(which(sapply(imputar, class)=="numeric"))]<-sapply(
  Filter(is.numeric, imputar),function(x) ImputacionCuant(x,"media"))

# Imputo todas las cualitativas por moda
imputar[,as.vector(which(sapply(imputar, class)=="factor"))]<-sapply(
  Filter(is.factor, imputar),function(x) ImputacionCuali(x,"moda"))

# Revisamos que no haya variables "char"
imputar[,as.vector(which(sapply(imputar, class)=="character"))] <- lapply(
  imputar[,as.vector(which(sapply(imputar, class)=="character"))] , factor)

# Verificamos que no queden valores missings
anyNA(imputar)

# Imputamos valores missings en variable children
noImputar$children<-ImputacionCuant(noImputar$children,"media")

#=========================================================================
# Formación del set de datos limpio
#=========================================================================
datosLimpios<-cbind(imputar,noImputar,varObj)

# Guardamos set de datos limpios para Feature engineering
saveRDS(datosLimpios,"datosLimpios")



Anexo 14: Código R para Feature Engineering.

# Realizamos carga de librerías
library(inspectdf)
library(corrplot)
library(ggplot2)
library(questionr)
library(psych)
library(car)
library(lubridate)
library(dummies)
library(caret)
source("Funciones_R.R")

# Realizamos carga de datos 
datosLimpios<-readRDS("datosLimpios")

#=========================================================================
# CREACIÓN DE VARIABLES
#=========================================================================
# De las variables Long_av y Lat_av creamos una variable distancia
datosLimpios$fe_longLat<-sqrt(datosLimpios$Long_av^2+datosLimpios$Lat_av^2)

# Determinamos fecha de cuándo se hizo la reserva:
datosLimpios$fe_fechaReserva<-(datosLimpios$fe_fechaArrival-datosLimpios$lead_time)

# Determinamos día de semana cuándo se hizo la reserva
datosLimpios$fe_diaSemanaReserva<-wday(datosLimpios$fe_fechaReserva)

# Determinamos día se semana de llegada a alojamiento
datosLimpios$fe_diaSemanaLlegada<-wday(datosLimpios$fe_fechaArrival)

# Determinamos el total de factura a pagar por la estadía. Supuestos
datosLimpios$fe_totalFactura<-((datosLimpios$stays_in_weekend_nights+
                                  datosLimpios$stays_in_week_nights)*
                                 datosLimpios$Av_DailyRate*1.3
                              )

#=========================================================================
# TRANSFORMACION DE VARIABLES
#=========================================================================
# Variables factor con demasiadas categorías, y que no pudieron ser agrupadas 
# anteriormente, pasan a numéricas por su frecuencia). Variables originales, 
# se eliminan.

# agente:
datosLimpios$fe_agent<-unname(table(datosLimpios$agent)[datosLimpios$agent])
# country
datosLimpios$fe_country<-unname(table(datosLimpios$country)[datosLimpios$country])
# company
datosLimpios$fe_company<-unname(table(datosLimpios$company)[datosLimpios$company])

# Pasamos a numéricas estas variables:
datosLimpios[,c(44:46)] <- lapply(datosLimpios[,c(44:46)], as.numeric)

#=========================================================================
# ELIMINACION DE VARIABLES
#=========================================================================
# Las siguientes variables no se usarán -> se eliminan del set de datos:
datosLimpios$arrival_date<-NULL # Por feature engineering
datosLimpios$reservation_status_date<-NULL # Por feature engineering
datosLimpios$prop_missings<-NULL # No aporta información
datosLimpios$is_canceled<-NULL # No aporta información
datosLimpios$Long_av<-NULL # Por correlación alta
datosLimpios$Lat_av<-NULL # Por correlación alta
datosLimpios$agent<-NULL # Por feature engineering
datosLimpios$country<-NULL # Por feature engineering
datosLimpios$company<-NULL # por feature engineering
datosLimpios$fe_fechaArrival<-NULL # No aporta información
datosLimpios$fe_fechaReserva<-NULL # No aporta información
datosLimpios$fe_fechaResStatus<-NULL # No aporta información

# Aplicamos logaritmos a variables asimétricas
datosTransf<-datosLimpios

datosTransf$fe_avDailyRate<-(log(datosTransf$Av_DailyRate))
datosTransf$fe_leadTime<-(log(datosTransf$lead_time))
datosTransf$fe_weekNights<-(log(datosTransf$stays_in_week_nights))
datosTransf$fe_weekendNights<-(log(datosTransf$stays_in_weekend_nights))
datosTransf$fe_specialRequest<-(log(datosTransf$total_of_special_requests))
datosTransf$fe2_totalFactura<-(log(datosTransf$fe_totalFactura))

datosTransf$Av_DailyRate<-NULL
datosTransf$lead_time<-NULL
datosTransf$stays_in_week_nights<-NULL
datosTransf$stays_in_weekend_nights<-NULL
datosTransf$total_of_special_requests<-NULL
datosTransf$fe_totalFactura<-NULL
datosTransf$fe_adults<-NULL
datosTransf$fe_babies<-NULL

# Revisamos gráficamente
eda <- inspect_num(datosTransf)
show_plot(eda)

#=========================================================================
# ESTANDARIZACIÓN Y DUMMIES
#=========================================================================
# SET DE DATOS 1
# Creación de dummies
colnames(Filter(is.factor,datosLimpios))
varFactor1<-c("hotel", "arrival_date_year", "arrival_date_month", 
             "arrival_date_day_of_month", "meal", "market_segment", 
             "distribution_channel", "is_repeated_guest", "reserved_room_type",
             "assigned_room_type", "deposit_type", "customer_type") 

# Variables factor pasan a dummies:
inputDummies<- dummy.data.frame(datosLimpios, varFactor1, sep = ".")


# Estandarización de variables numéricas
colnames(Filter(is.numeric,datosLimpios))
varNumeric1<-c("lead_time", "arrival_date_week_number", "stays_in_weekend_nights",
               "stays_in_week_nights", "Av_DailyRate", "adults", "children", "babies",
               "previous_cancellations", "previous_bookings_not_canceled", 
               "booking_changes", "days_in_waiting_list", "required_car_parking_spaces",
               "total_of_special_requests", "fe_longLat", "fe_diaSemanaReserva",
               "fe_diaSemanaLlegada", "fe_totalFactura", "fe_agent", "fe_country",
               "fe_company")

means <-apply(inputDummies[,varNumeric1],2,mean) 
sds<-sapply(inputDummies[,varNumeric1],sd)
contSD<-scale(inputDummies[,varNumeric1], center = means, scale = sds)
contExcluir<-which(colnames(inputDummies)%in%varNumeric1)

# Construimos el set de datos final
datosFinal<-cbind(inputDummies[,-contExcluir],contSD)

# Guardamos los datos:
saveRDS(datosFinal,"datosFinal")

# Realizamos partición de datos 70%, 20% 10%
# 70% para train
train <- createDataPartition(y = datosFinal$varObj, p = 0.70, list = FALSE, times = 1)
datosTrain<-datosFinal[train,]
saveRDS(datosTrain,"datosTrain")

# 20% para validación
aux<-datosFinal[-train,]
validacion<-createDataPartition(y = aux$varObj, p = 0.6667, list = FALSE, times = 1)
datosValidacion<-aux[validacion,]
saveRDS(datosValidacion,"datosValidacion")

# 10% para test
datosTest<-aux[-validacion,]
saveRDS(datosTest,"datosTest") 

# SET DE DATOS 2:
# Construimos segundo set de datos
colnames(Filter(is.factor,datosTransf))

varFactor2<-c("hotel", "arrival_date_year", "arrival_date_month", 
              "arrival_date_day_of_month", "meal", "market_segment", 
              "distribution_channel", "is_repeated_guest", "reserved_room_type",
              "assigned_room_type", "deposit_type", "customer_type"
              )

# Variables factor pasan a dummies:
inputDummies2<- dummy.data.frame(datosTransf, varFactor2, sep = ".")

# Estandarización de variables numéricas
colnames(Filter(is.numeric,datosTransf))

varNumeric2<-c("arrival_date_week_number", "adults", "children", "babies", 
               "previous_cancellations", "previous_bookings_not_canceled", 
               "booking_changes", "days_in_waiting_list", "required_car_parking_spaces", 
               "fe_longLat", "fe_diaSemanaReserva", "fe_diaSemanaLlegada", "fe_agent",
               "fe_country", "fe_company"
               )

means2 <-apply(inputDummies2[,varNumeric2],2,mean) 
sds2<-sapply(inputDummies2[,varNumeric2],sd)
contSD2<-scale(inputDummies2[,varNumeric2], center = means2, scale = sds2)
contExcluir2<-which(colnames(inputDummies2)%in%varNumeric2)

# Construimos el set de datos final
datosFinal2<-cbind(inputDummies2[,-contExcluir2],contSD2)

# Verificamos datos perdidos en el set de datos
anyNA(datosFinal2)

# Guardamos los datos:
saveRDS(datosFinal2,"datosFinal2")

# Realizamos partición de datos 70%, 20% 10%
train2 <- createDataPartition(y = datosFinal2$varObj, p = 0.70, list = FALSE, times = 1)
datosTrain2<-datosFinal2[train2,]
saveRDS(datosTrain2,"datosTrain2")

# 20% para validación
aux2<-datosFinal2[-train2,]
validacion2<-createDataPartition(y = aux2$varObj, p = 0.6667, list = FALSE, times = 1)
datosValidacion2<-aux2[validacion2,]
saveRDS(datosValidacion2,"datosValidacion2")

# 10% para test
datosTest2<-aux2[-validacion2,]
saveRDS(datosTest2,"datosTest2")



Anexo 15: Diagrama de Correlaciones para variables numéricas del set de datos.

Diagrama de correlaciones entre variables numéricas.

Diagrama de correlaciones entre variables numéricas.

De la gráfica de correlaciones, podemos ver que las variables no presentan una correlación alta entre ellas -mayor al 70%-, con excepción de la variable fe_longLat vs Long_av y Lat_av, que son las variables que la formaron. Estas 2 últimas variables se eliminan del set de datos. La otra variable que muestra una correlación relativamente elevada es fe_totalFactura con sus variables componentes. En este caso la correlación no es elevada, por lo que se mantiene en el set de datos.

Anexo 16: Histograma de variables previa aplicación de transformaciones.

Histogramas de variables numéricas previa transformación ln().

Histogramas de variables numéricas previa transformación ln().

Gráficas muestran los histogramas de las distribuciones de las variables antes del proceso de transformación aplicada.

Anexo 17: Histograma de variables luego de transformaciones aplicadas.

Histogramas de variables numéricas luego de transformación ln().

Histogramas de variables numéricas luego de transformación ln().

Gráficas muestran los histogramas de las distribuciones de las variables después del proceso de transformación aplicada. Puede observarse variables más centradas.

Anexo 18: Importancia de variables (Accuracy Decrease) por Random Forest.

# Variable MeanDecreaseAccuracy % % Acum Seleccionada?
1 fe_country 54.791 9,0% 9,0% si
2 lead_time 49.011 8,0% 17,0% si
3 total_of_special_requests 43.870 7,2% 24,2% si
4 fe_agent 43.294 7,1% 31,3% si
5 fe_longLat 41.235 6,8% 38,1% si
6 deposit_type.Deposit 31.837 5,2% 43,3% si
7 deposit_type.No_Deposit 31.636 5,2% 48,5% si
8 fe_totalFactura 22.526 3,7% 52,2% si
9 market_segment.Online_TA 21.526 3,5% 55,7% si
10 Av_DailyRate 20.366 3,3% 59,1% si
11 previous_cancellations 18.996 3,1% 62,2% si
12 customer_type.Transient 15.692 2,6% 64,8% si
13 assigned_room_type.A 14.293 2,3% 67,1% si
14 arrival_date_week_number 13.644 2,2% 69,4% si
15 required_car_parking_spaces 13.135 2,2% 71,5% si
16 customer_type.Transient_Party 13.004 2,1% 73,7% si
17 arrival_date_year.2015 11.297 1,9% 75,5% si
18 stays_in_week_nights 10.685 1,8% 77,3% si
19 booking_changes 8.528 1,4% 78,7% si
20 arrival_date_year.2017 8.158 1,3% 80,0% si
21 arrival_date_year.2016 7.702 1,3% 81,3% si
22 market_segment.Groups 7.375 1,2% 82,5% si
23 distribution_channel.TATO 6.997 1,1% 83,6% si
24 hotel.Resort_Hotel 6.932 1,1% 84,8% si
25 market_segment.Other 6.873 1,1% 85,9% si
26 hotel.City_Hotel 6.666 1,1% 87,0% si
27 stays_in_weekend_nights 6.581 1,1% 88,1% si
28 fe_diaSemanaLlegada 6.362 1,0% 89,1% si
29 market_segment.Offline_TATO 6.318 1,0% 90,1% si
30 reserved_room_type.A 6.070 1,0% 91,1% si
31 fe_diaSemanaReserva 5.247 0,9% 92,0% no
32 assigned_room_type.D 4.672 0,8% 92,8% no
33 adults 4.537 0,7% 93,5% no
34 distribution_channel.Direct 3.147 0,5% 94,0% no
35 arrival_date_month.Q3 3.093 0,5% 94,5% no
36 meal.Bed_Breakfast 2.838 0,5% 95,0% no
37 arrival_date_month.Q2 2.779 0,5% 95,5% no
38 arrival_date_month.Q4 2.642 0,4% 95,9% no
39 arrival_date_month.Q1 2.567 0,4% 96,3% no
40 reserved_room_type.D 2.386 0,4% 96,7% no
41 meal.Half_Board 2.036 0,3% 97,0% no
42 previous_bookings_not_canceled 1.943 0,3% 97,3% no
43 arrival_date_day_of_month.H2 1.941 0,3% 97,7% no
44 arrival_date_day_of_month.H1 1.870 0,3% 98,0% no
45 meal.Other 1.868 0,3% 98,3% no
46 children 1.573 0,3% 98,5% no
47 fe_company 1.508 0,2% 98,8% no
48 distribution_channel.Other 1.161 0,2% 99,0% no
49 is_repeated_guest.1 868 0,1% 99,1% no
50 is_repeated_guest.0 854 0,1% 99,3% no
51 reserved_room_type.Other 834 0,1% 99,4% no
52 days_in_waiting_list 814 0,1% 99,5% no
53 customer_type.Other 657 0,1% 99,6% no
54 assigned_room_type.E 633 0,1% 99,7% no
55 reserved_room_type.E 446 0,1% 99,8% no
56 assigned_room_type.Other 428 0,1% 99,9% no
57 assigned_room_type.F 422 0,1% 100,0% no
58 assigned_room_type.C 167 0,0% 100,0% no
59 assigned_room_type.G 109 0,0% 100,0% no
60 babies 3 0,0% 100,0% no
———————————————— ——————— ——– ——– —————
TOTAL 609.443 100,0%

Cuadro muestra el output de la función variableImportance() de la librería Random Forest, para el modelo 4. Segunda columna muestra el nombre de la variable del set de datosLimpios. Las variables están ordenadas de forma decreciente por su efecto negativo sobre el accuracy. (MeanDecreaseAccuracy). Se seleccionaron aquellas variables que acumulaban un efecto en el accuracy de un 90% (5a columna). Última columna booleana indica si la variable fue seleccionada para futuros modelos o no. Se seleccionaron 30 variables (número redondo).

Anexo 19: Código R para tunning algoritmos Random Forest en Caret.

#=========================================================================
# Modelo Random Forest
#=========================================================================
# Realizamos carga de librerías a utilizar en la práctica
library(caret)
library(randomForest)
library(questionr)
library(pROC)

# Realizamos carga de los sets de datos
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")

#-------------------------------------------------------------------------
# Modelo 1: Tuning de hiperparámetros: # de árboles
#-------------------------------------------------------------------------
# En grilla de hiperparamétros incluimos opción de downsample para manejar 
# problema de desbalanceo en variable objetivo.
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      sampling="down",
                      classProbs=TRUE)

# Probamos con mtry = sqrt(n variables) 
rfgrid <- expand.grid(mtry = 8)

modellist <- list() # creamos lista vacía

# Probamos 4 tamaños de árbol y 1 semilla
for (ntree in c(1000,1500,2000,2500)){
  set.seed(1234)
  
# Entrenamos modelo 
  rf1<- train(factor(varObj)~., # Usamos las 60 variables
              data=datosTrain,
              method="rf",
              trControl=control,
              tuneGrid=rfgrid,
              linout = FALSE,
              ntree=ntree,
              nodesize=10,
              replace=TRUE,
             importance=TRUE)
  
  key <- toString(ntree)
  modellist[[key]] <- rf1
}

# Obtenemos los resultados. Buen modelo con 2.000 árboles.
results <- resamples(modellist)
summary(results)

#-------------------------------------------------------------------------
# Modelo 2: Vemos si mejora accuracy con número distinto de árboles
#-------------------------------------------------------------------------
for (ntree in c(500,2500,3000,3500,4000,5000)){
  
  set.seed(1234) # Misma semilla
  rf2<- train(factor(varObj)~., # Usamos las 60 variables
              data=datosTrain,
              method="rf",
              trControl=control,
              tuneGrid=rfgrid,
              linout = FALSE,
              ntree=ntree,
              nodesize=10,
              replace=TRUE,
              importance=TRUE)
  
  key <- toString(ntree)
  modellist[[key]] <- rf2
}

# Mostramos resultados. 
results2 <- resamples(modellist)
summary(results2)

#-------------------------------------------------------------------------
# Modelo 3, tuneado de mtry
#-------------------------------------------------------------------------
# Verificamos que el mtry escogido = 8 es bueno
rfgrid <- expand.grid(mtry = c(3,4,5,6,7,8,9,10))

set.seed(1234) # Misma semilla para mantener muestra

# Entrenamos el modelo
rf3<- train(factor(varObj)~., # Usamos las 60 variables
            data=datosTrain,
            method="rf",
            trControl=control,
            tuneGrid=rfgrid,
            linout = FALSE,
            ntree=2000,
            nodesize=10,
            replace=TRUE,
            importance=TRUE)

# Creamos subset de datos del mejor modelo, dado mtry
datos3<-subset(rf3$pred,mtry==8)

# Mostramos matriz de confusión para el mejor modelo:
confusionMatrix(datos3$pred,datos3$obs)

# Calculamos AUC
# roc1<-multiclass.roc(as.numeric(rf3$finalModel$y),as.numeric(rf3$finalModel$predicted))
# roc1$auc

# Realizamos predicciones con modelo downsample, 2.000 arboles y Mtry = 8
p1<-predict(rf3,datosValidacion[,-40])

# Creamos Matriz de confusión
confusionMatrix(datosValidacion$varObj,p1)

# Calculamos AUC:
# roc2<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(p1))
# roc2$auc

#-------------------------------------------------------------------------
# Modelo 4: Probamos sin opción de sampling con 2.000 arboles y mtry = 8
#-------------------------------------------------------------------------
# Modificamos grilla de control en opción sampling
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      classProbs=TRUE)

rfgrid <- expand.grid(mtry = 8)

set.seed(1234) # Misma semilla

# Entrenamos el modelo 
rf4<- train(factor(varObj)~., # Usamos las 60 variables
            data=datosTrain,
            method="rf",
            trControl=control,
            tuneGrid=rfgrid,
            linout = FALSE,
            ntree=2000,
            nodesize=10,
            replace=TRUE,
            importance=TRUE)

# Mostramos matriz de confusión con resultados:
confusionMatrix(rf4$pred$pred,rf4$pred$obs)

# roc3<-multiclass.roc(as.numeric(rf4$finalModel$y),as.numeric(rf4$finalModel$predicted))
# roc3$auc

# Realizamos predicciones para el modelo sobre set de validación
p2<-predict(rf4,datosValidacion[,-40])
# Creamos Matriz de confusión
confusionMatrix(datosValidacion$varObj,p2)

# Calculamos AUC:
# roc4<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(p2))
# roc4$auc

# Obtenemos las variables mas importantes
rf4$finalModel$importance

# Las variables más importantes:
mejVariables<-c("fe_country","lead_time","total_of_special_requests","fe_agent",
"fe_longLat","deposit_type.Deposit","deposit_type.No_Deposit","fe_totalFactura",
"market_segment.Online_TravelAgents","Av_DailyRate","previous_cancellations",
"customer_type.Transient","assigned_room_type.A","arrival_date_week_number",
"required_car_parking_spaces","customer_type.Transient_Party",
"arrival_date_year.2015","stays_in_week_nights","booking_changes",
"arrival_date_year.2017","arrival_date_year.2016","market_segment.Groups",
"distribution_channel.TravAgents_TurismOperator","hotel.Resort_Hotel",
"market_segment.Other","hotel.City_Hotel","stays_in_weekend_nights",
"fe_diaSemanaLlegada","market_segment.Offline_TravAgentsTurOperators",
"reserved_room_type.A")

# paste(mejVariables,collapse="+")

#-------------------------------------------------------------------------
# Modelo 5, 2.000 árboles, mtry cambia a 6. Solo variables más importantes.
#-------------------------------------------------------------------------
# Grilla de control la mantenemos sin opción sampling
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      classProbs=TRUE)

# Modificamos mtry por menor número de variables
rfgrid <- expand.grid(mtry = 6)

set.seed(1234) # Misma semilla

# Entrenamos el modelo
rf5<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
                          fe_agent+fe_longLat+deposit_type.Deposit+
                          deposit_type.No_Deposit+fe_totalFactura+
                          market_segment.Online_TravelAgents+Av_DailyRate+
                          previous_cancellations+customer_type.Transient+
                          assigned_room_type.A+arrival_date_week_number+
                          required_car_parking_spaces+customer_type.Transient_Party+
                          arrival_date_year.2015+stays_in_week_nigts+
                          booking_changes+arrival_date_year.2017+arrival_date_year.2016+
                          market_segment.Groups+
                          distribution_channel.TravAgents_TurismOperator+
                          hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
                          stays_in_weekend_nights+fe_diaSemanaLlegada+
                          market_segment.Offline_TravAgentsTurOperators+
                          reserved_room_type.A,
            data=datosTrain,
            method="rf",
            trControl=control,
            tuneGrid=rfgrid,
            linout = FALSE,
            ntree=2000,
            nodesize=10,
            replace=TRUE,
            importance=TRUE)

# Vemos resultados
confusionMatrix(rf5$pred$pred,rf5$pred$obs)

# Calculamos AUC
# roc5<-multiclass.roc(as.numeric(rf5$finalModel$y),as.numeric(rf5$finalModel$predicted))
# roc5$auc

# Realizamos predicciones sobre el set de datos validación
p3<-predict(rf5,datosValidacion[,-40])
# Creamos Matriz de confusión
confusionMatrix(datosValidacion$varObj,p3)

# Calculamos AUC:
# roc6<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(p3))
# roc6$auc

#-------------------------------------------------------------------------
# Validación cruzada repetida modelo tuneado
#-------------------------------------------------------------------------
# Cambiamos semilla para ver una muestra distinta
set.seed(4321)

# Definimos grilla de control
control<-trainControl(method = "repeatedcv",
                      number=10,
                      repeats=10,
                      savePredictions = "all",
                      classProbs=TRUE) 

rfgrid <-expand.grid(mtry=6)

# Entrenamos el modelo
modRandomForest<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
                          fe_agent+fe_longLat+deposit_type.Deposit+
                          deposit_type.No_Deposit+fe_totalFactura+
                          market_segment.Online_TravelAgents+Av_DailyRate+
                          previous_cancellations+customer_type.Transient+
                          assigned_room_type.A+arrival_date_week_number+
                          required_car_parking_spaces+customer_type.Transient_Party+
                          arrival_date_year.2015+stays_in_week_nigts+
                          booking_changes+arrival_date_year.2017+arrival_date_year.2016+
                          market_segment.Groups+
                          distribution_channel.TravAgents_TurismOperator+
                          hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
                          stays_in_weekend_nights+fe_diaSemanaLlegada+
                          market_segment.Offline_TravAgentsTurOperators+
                          reserved_room_type.A,
                        data=datosTrain,
                        method="rf",
                        trControl=control,
                        tuneGrid=rfgrid,
                        linout = FALSE,
                        ntree=2000,
                        nodesize=10,
                        sampsize=2000,
                        replace=TRUE
                        )

# Revisamos performance con matriz de confusión
confusionMatrix(modRandomForest$pred$pred,modRandomForest$pred$obs)

preditest<-modRandomForest$pred

# Chequeamos que resultados se mantengan
confusionMatrix(preditest$pred,preditest$obs)

preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL

# Definimos función de cálculo de Accuracy:
tasaAciertos<-function(x,y) {
  confu<-confusionMatrix(x,y)
  tasa<-confu[[3]][1]
  return(tasa)
}

# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
  paso1<-preditest[which(preditest$Rep==repi),]
  acc=tasaAciertos(paso1$pred,paso1$obs)  
  medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"

# Guardamos datos de validación cruzada
saveRDS(medias,"modRandomForest")



Anexo 20: Output Modelo Random Forest 1 / hiperparámetros.

Output modelo Random Forest 1.

Output modelo Random Forest 1.

Modelo 1 de Random Forest buscaba la determinación de la mejor combinación de hiperparámetros. Se puede ver que el accuracy medio no varia demasiado con el número de árboles; sin embargo, aumenta en la medida que éstos lo hacen. El valor fluctúa en torno a los 70,33% y 70,38% -recordar que este modelo tenía incorporada la opción “downsample”. Un segundo modelo amplía la búsqueda para ver qué ocurre con más árboles.

Anexo 21: Output Modelo Random Forest 2 / Número de árboles.

Output modelo Random Forest 2.

Output modelo Random Forest 2.

Un segundo modelo Random Forest amplió la búsqueda incorporando más árboles a los modelos. Se puede ver que el accuracy aumenta con un mayor número de árboles, pero que este aumento no es importante, por lo que no vale la pena complejizar el modelo por una mejora muy marginal del accuracy. Selección final fue por 2.000 árboles.

Anexo 22: Matriz de confusión modelo Random Forest 3 sobre datos train.

Canceled Check_Out No_Show Total
Canceled 21.714 5.325 79 27.118
Check_Out 3.807 37.412 146 41.365
No_Show 4.591 9.880 620 15.091
Total 30.112 52.617 845 83.574
———– ———– ———– ———– ———–
Diagonal 59.746
Accuracy 71,5%



Anexo 23: Matriz de confusión modelo Random Forest 3 sobre datos Validación.

Canceled Check_Out No_Show Total
Canceled 6.385 852 1.367 8.604
Check_Out 1.838 10.394 2.802 15.034
No_Show 24 29 189 242
Total 8.247 11.275 4.358 23.880
———– ———– ———– ———– ———–
Diagonal 16.968
Accuracy 71,1%



Anexo 24: Código R para tunning de algoritmo Multinomial en Caret.

#=========================================================================
# Modelo Multinomial
#=========================================================================
# Realizamos carga de librerías a utilizar en la práctica
library(caret)
library(questionr)
library(pROC)

# Realizamos carga de los sets de datos
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")

#=========================================================================
# Modelo 1 30 variables importantes de Random Forest
#=========================================================================

# Definimos la grilla de control
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      classProbs=TRUE)

# Modelo
set.seed(1234)  # Misma semilla que Random Forest
multinom1 <- train(varObj~fe_country+lead_time+total_of_special_requests+
                     fe_agent+fe_longLat+deposit_type.Deposit+
                     deposit_type.No_Deposit+fe_totalFactura+
                     market_segment.Online_TravelAgents+Av_DailyRate+
                     previous_cancellations+customer_type.Transient+
                     assigned_room_type.A+arrival_date_week_number+
                     required_car_parking_spaces+customer_type.Transient_Party+
                     arrival_date_year.2015+stays_in_week_nights+booking_changes+
                     arrival_date_year.2017+arrival_date_year.2016+
                     market_segment.Groups+
                     distribution_channel.TravAgents_TurismOperator+
                     hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
                     stays_in_weekend_nights+fe_diaSemanaLlegada+
                     market_segment.Offline_TravAgentsTurOperators+
                     reserved_room_type.A, 
                   data = datosTrain, 
                   method = "multinom",
                   trControl = control
                   )

# Revisamos resultados                  
multinom1

a<-subset(multinom1,multinom1$pred$decay==0)
a<-as.data.frame(a[5]$pred)

# Calculamos matriz de confusión para mejor modelo, decay =0
confusionMatrix(a$pred,a$obs)

# Realizamos predicciones sobre el set de validación
p1<-predict(multinom1,datosValidacion[,-40])

# Vemos la matriz de confusión
confusionMatrix(p1,datosValidacion$varObj)

#-------------------------------------------------------------------------
# Validación cruzada repetida
#-------------------------------------------------------------------------

set.seed(4321) # Cambiamos semilla para ver muestra distinta
control<-trainControl(method = "repeatedcv",
                      number=10,
                      repeats=10,
                      savePredictions = "all",
                      classProbs=TRUE) 
# Corremos modelo
multinom2 <- train(varObj~fe_country+lead_time+total_of_special_requests+
                     fe_agent+fe_longLat+deposit_type.Deposit+
                     deposit_type.No_Deposit+fe_totalFactura+
                     market_segment.Online_TravelAgents+Av_DailyRate+
                     previous_cancellations+customer_type.Transient+
                     assigned_room_type.A+arrival_date_week_number+
                     required_car_parking_spaces+customer_type.Transient_Party+
                     arrival_date_year.2015+stays_in_week_nights+booking_changes+
                     arrival_date_year.2017+arrival_date_year.2016+
                     market_segment.Groups+
                     distribution_channel.TravAgents_TurismOperator+
                     hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
                     stays_in_weekend_nights+fe_diaSemanaLlegada+
                     market_segment.Offline_TravAgentsTurOperators+
                     reserved_room_type.A, 
                   data = datosTrain, 
                   method = "multinom",
                   trControl = control
                   )

# Rescatamos las predicciones como data frame
multinom2$pred
preditest<-multinom2$pred

# Obtenemos las predicciones del mejor modelo entrenado
a<-subset(preditest,preditest$decay==1e-04)

# Matriz de confusión del mejor modelo
confusionMatrix(a$pred,a$obs)

# Construimos tabla para accuracy por repetición
preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL

# Funcion de claculo de accuracy
tasaAciertos<-function(x,y) {
  confu<-confusionMatrix(x,y)
  tasa<-confu[[3]][1]
  return(tasa)
}

# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
  paso1<-preditest[which(preditest$Rep==repi),]
  acc=tasaAciertos(paso1$pred,paso1$obs)  
  medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"

# Revisamos los resultados
medias

# Guardamos los datos de los accuracy
saveRDS(medias,"modMultinomial")



Anexo 25: Código R para tunning de algoritmo Xgboost en Caret.

#=========================================================================
# Modelo XGboost
#=========================================================================

# Cargamos librerías necesarias
library(caret)
library(pROC)

# Realizamos carga de datos:
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")
datosTest<-readRDS("datosTest")

#------------------------------------------------------------------------
# Modelo Xgboost1: tuneado de hiperparámetros
#------------------------------------------------------------------------
# Definimos grilla de hiperparámetros a definir
xgbmgrid<-expand.grid(min_child_weight=c(5,10,15,20),
                      eta=c(0.1,0.05,0.03,0.01,0.001),
                      nrounds=c(100,500,1000,5000),
                      max_depth=6,
                      gamma=0,
                      colsample_bytree=1,
                      subsample=1)

# Definimos grilla de control, misma que en algoritmos anteriores
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      classProbs=TRUE)

set.seed(1234) # Misma semilla con la que hemos trabajado

# Entrenamos modelo
xgbm1<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
               fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
               fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
               previous_cancellations+customer_type.Transient+assigned_room_type.A+
               arrival_date_week_number+required_car_parking_spaces+
               customer_type.Transient_Party+arrival_date_year.2015+
                stays_in_week_nights+booking_changes+arrival_date_year.2017+
                arrival_date_year.2016+market_segment.Groups+
                distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
                market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
                fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
               reserved_room_type.A,
             data=datosTrain,
             method="xgbTree"
             ,trControl=control,
             tuneGrid=xgbmgrid,
             verbose=FALSE)

# Revisamos los resultados
xgbm1

# GRaficamos
plot(xgbm1)

#------------------------------------------------------------------------
# Modelo Xgboost2: Ajustamos iteraciones a rango 2.000 - 4.000
#------------------------------------------------------------------------
# Se redefine la grilla de hiperparámetros para ver que ocurre con los árboles
xgbmgrid<-expand.grid(min_child_weight=c(5),
                      eta=c(0.05),
                      nrounds=c(2000,3000,4000),
                      max_depth=6,
                      gamma=0,
                      colsample_bytree=1,
                      subsample=1)

# Mantenemos la grilla de control sin cambios
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      classProbs=TRUE)

set.seed(1234) # Misma semilla con la que hemos trabajado

# Entrenamos modelo 
xgbm2<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
               fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
               fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
               previous_cancellations+customer_type.Transient+assigned_room_type.A+
               arrival_date_week_number+required_car_parking_spaces+
               customer_type.Transient_Party+arrival_date_year.2015+
                stays_in_week_nights+booking_changes+arrival_date_year.2017+
                arrival_date_year.2016+market_segment.Groups+
                distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
                market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
                fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
               reserved_room_type.A,
              data=datosTrain,
              method="xgbTree",
              trControl=control,
              tuneGrid=xgbmgrid,
              verbose=FALSE)

# Revisamos gráficamente
plot(xgbm2)

# Revisamos resultados de modelo óptimo
mod2<-subset(xgbm2$pred,xgbm2$pred$nrounds==3000)

# Calculamos matriz de confusión
confusionMatrix(mod2$pred,mod2$obs)

# Calculamos AUC
# roc1<-multiclass.roc(as.numeric(mod2$pred),as.numeric(mod2$obs))
# roc1$auc

# Revisamos proyecciones sobre set de datos de validación
predXgb<-predict(xgbm2,datosValidacion[,-40])

# Calculamos matriz de confusión
confusionMatrix(datosValidacion$varObj,predXgb)

# Calculamos AUC
# roc2<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(predXgb))
# roc2$auc

#-------------------------------------------------------------------------
# Validación cruzada repetida modelo tuneado
#-------------------------------------------------------------------------

set.seed(4321) # Cambiamos semilla para ver una muestra distinta

# Creamos grilla de control
control<-trainControl(method = "repeatedcv",
                      number=10,
                      repeats=10,
                      savePredictions = "all",
                      classProbs=TRUE
                      )

# Definimos grilla de hiperparámetros igual al mejor modelo
xgbmgrid<-expand.grid(min_child_weight=c(5),
                      eta=c(0.05),
                      nrounds=c(3000),
                      max_depth=6,
                      gamma=0,
                      colsample_bytree=1,
                      subsample=1
                      )

# Entrenamos modelo
xgoost<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
               fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
               fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
               previous_cancellations+customer_type.Transient+assigned_room_type.A+
               arrival_date_week_number+required_car_parking_spaces+
               customer_type.Transient_Party+arrival_date_year.2015+
                stays_in_week_nights+booking_changes+arrival_date_year.2017+
                arrival_date_year.2016+market_segment.Groups+
                distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
                market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
                fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
               reserved_room_type.A,
              data=datosTrain,
              method="xgbTree",
              trControl=control,
              tuneGrid=xgbmgrid,
              verbose=FALSE
              )

# Revisamos resultados con matriz de confusión
confusionMatrix(xgoost$pred$pred,xgoost$pred$obs)

preditest<-xgoost$pred

# Chequeamos que valores no hayan cambiado
confusionMatrix(preditest$pred,preditest$obs)

preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL

# Definimos función de accuracy:
tasaAciertos<-function(x,y) {
  confu<-confusionMatrix(x,y)
  tasa<-confu[[3]][1]
  return(tasa)
}

# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
  paso1<-preditest[which(preditest$Rep==repi),]
  acc=tasaAciertos(paso1$pred,paso1$obs)  
  medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"

# Guardamos datos de validación cruzada
saveRDS(medias,"modXgboost")

#-------------------------------------------------------------------------
# Predicciones sobre el set de test
#-------------------------------------------------------------------------
# Realizamos predicciones sobre el set de test:
pred<-predict(xgbm2,datosTest[,-40])

# Calculamos matriz de confusión
confusionMatrix(datosTest$varObj,pred)



Anexo 26: Output hiperparámetros modelo Xgboost1.

Hiperparámetros de modelo Xgboost1.

Hiperparámetros de modelo Xgboost1.

Como se comentó, el objetivo del primer modelo Xgboost fue determinar los mejores hiperparámetros para modelos posteriores. En la gráfica, podemos ver que un alto número de iteraciones -línea roja que corresponde a 5.000- domina en todos los escenarios. El máximo accuracy se produce con shrinkage de 0,05 y min_chil_weight de 5. existe una gran diferencia entre 1.000 a 5.000 iteraciones, por lo que el estudio de esta particular zona, para acotar el accuracy a un menor número de ellas fue el objetivo del modelo Xgboost 2.

Anexo 27: Output hiperparámetros modelo Xgboost2.

Accuracy vs número de iteraciones en modelo Xgboost2.

Accuracy vs número de iteraciones en modelo Xgboost2.

Un segundo modelo Xgboost tuvo como objetivo, determinar el mejor número de árboles en el espacio 1.000 y 4.000 árboles. La gráfica muestra que el mejor accuracy se obtiene con 3.000 árboles.


Anexo 28: Código R para tunning de algoritmo Red Neuronal en Caret.

#=========================================================================
# Modelo Multinomial
#=========================================================================
# Realizamos carga de librerías a utilizar en la práctica
library(caret)
library(questionr)
library(pROC)

# Realizamos carga de los sets de datos
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")

#=========================================================================
# Modelo 1 30 variables importantes de Random Forest
#=========================================================================

# Definimos la grilla de control
control<-trainControl(method = "cv",
                      number=4,
                      savePredictions = "all",
                      classProbs=TRUE)

# Modelo
set.seed(1234)  # Misma semilla que Random Forest
multinom1 <- train(varObj~fe_country+lead_time+total_of_special_requests+
                     fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
                     fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
                     previous_cancellations+customer_type.Transient+assigned_room_type.A+
                     arrival_date_week_number+required_car_parking_spaces+
                     customer_type.Transient_Party+arrival_date_year.2015+
                     stays_in_week_nights+booking_changes+arrival_date_year.2017+
                     arrival_date_year.2016+market_segment.Groups+
                     distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
                     market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
                     fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
                     reserved_room_type.A, 
                   data = datosTrain, 
                   method = "multinom",
                   trControl = control
                   )

# Revisamos resultados                  
multinom1

a<-subset(multinom1,multinom1$pred$decay==0)
a<-as.data.frame(a[5]$pred)

# Calculamos matriz de confusión para mejor modelo, decay =0
confusionMatrix(a$pred,a$obs)

# Realizamos predicciones sobre el set de validación
p1<-predict(multinom1,datosValidacion[,-40])

# Vemos la matriz de confusión
confusionMatrix(p1,datosValidacion$varObj)

#-------------------------------------------------------------------------
# Validación cruzada repetida
#-------------------------------------------------------------------------

set.seed(4321) # Cambiamos semilla para ver muestra distinta
control<-trainControl(method = "repeatedcv",
                      number=10,
                      repeats=10,
                      savePredictions = "all",
                      classProbs=TRUE) 
# Corremos modelo
multinom2 <- train(varObj~fe_country+lead_time+total_of_special_requests+
                     fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
                     fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
                     previous_cancellations+customer_type.Transient+assigned_room_type.A+
                     arrival_date_week_number+required_car_parking_spaces+
                     customer_type.Transient_Party+arrival_date_year.2015+
                     stays_in_week_nights+booking_changes+arrival_date_year.2017+
                     arrival_date_year.2016+market_segment.Groups+
                     distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
                     market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
                     fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
                     reserved_room_type.A, 
                   data = datosTrain, 
                   method = "multinom",
                   trControl = control
                   )

# Rescatamos las predicciones como data frame
multinom2$pred
preditest<-multinom2$pred

# Obtenemos las predicciones del mejor modelo entrenado
a<-subset(preditest,preditest$decay==1e-04)

# Matriz de confusión del mejor modelo
confusionMatrix(a$pred,a$obs)

# Construimos tabla para accuracy por repeticion
preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL

# Función de cálculo de accuracy
tasaAciertos<-function(x,y) {
  confu<-confusionMatrix(x,y)
  tasa<-confu[[3]][1]
  return(tasa)
}

# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
  paso1<-preditest[which(preditest$Rep==repi),]
  acc=tasaAciertos(paso1$pred,paso1$obs)  
  medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"

# Revisamos los resultados
medias

# Guardamos los datos de los accuracy
saveRDS(medias,"modMultinomial")



Anexo 29: Matriz de confusión Red Neuronal avNNet sobre datos de entrenamiento.

Canceled Check_Out No_Show Total
Canceled 24.042 4.022 225 28.289
Check_Out 6.070 48.595 620 55.285
No_Show 0 0 0 0
Total 30.112 52.617 845 83.574
———– ———– ———– ———– ———–
Diagonal 72.637
Accuracy 86,9%



Anexo 30: Matriz de confusión Red Neuronal avNNet sobre datos de validación.

Canceled Check_Out No_Show Total
Canceled 6.654 1.950 0 8.604
Check_Out 949 14.085 0 15.034
No_Show 63 179 0 242
Total 7.666 16.214 0 23.880
———– ———– ———– ———– ———–
Diagonal 20.739
Accuracy 86,8%



Anexo 31: Código R proceso de validación cruzada repetida en Caret.

#-----------------------------------------------------------------
# VCR Modelos Caret
#-----------------------------------------------------------------

# Realizamos carga de los accuracy de las cv para los 4 modelos
rf<-readRDS("modRandomForest")
multinom<-readRDS("modMultinomial")
xgboost<-readRDS("modXgboost")
nnet<-readRDS("modRnnet")

# Insertamos columna con nombre del modelo
rf$modelo<-"RndForest"
multinom$modelo<-"Multinomial"
xgboost$modelo<-"Xgboost"
nnet$modelo<-"Nnet"

# Unimos los accuracy en solo 1 data frame
union<-rbind(rf,multinom,xgboost,nnet)

# Reordenamos el set de datos con los modelos por su mediana
union$modelo <- with(union,reorder(modelo,accuracy, median))

# Graficamos
par(cex.axis=0.8,las=2)
boxplot(data=union,accuracy~modelo,
        main="Comparación Accuracy modelos \n librería Caret - Cross Validation", 
        col="orange1")

#-----------------------------------------------------------------
# VCR Modelos H2O
#-----------------------------------------------------------------
# Realizamos carga de datos de las VCR de H2O
modH2o<-read.csv2("modelos_h2o.txt",sep="\t")

# Reordenamos el set de datos con los modelos por su mediana
modH2o$modelo <- with(modH2o,reorder(modelo,accuracy, median))

# Graficamos
par(cex.axis=0.8,las=1)
boxplot(data=modH2o,accuracy~modelo,
        main="Comparación Accuracy modelos \n librería H2O - Cross Validation", 
        col="orange1")

#-----------------------------------------------------------------
# Mejor modelo Caret vs mejor modelo H2O
#-----------------------------------------------------------------
a<-subset(union,union$modelo=="Xgboost") # Filtramos para los datos de Xgboost
a$modelo<-"Xgboost_Caret" # Renombramos la columna 
b<-subset(modH2o,modH2o$modelo=="Stack_GLM_H2O")# Filtramos para los datos de Stack GLM
b$modelo<-"Stack_GLM_H20" # Renombramos la columna
c<-rbind(a,b) # Unimos los frames

# Reordenamos el set de datos con los modelos por su mediana
c$modelo <- with(c,reorder(modelo,accuracy, median))

# Graficamos
par(cex.axis=0.8,las=1)
boxplot(data=c,accuracy~modelo,
        main="Comparación mejores modelos Caret y H2O", 
        col="orange1")



Anexo 32: Accuracy proceso VCR librería Caret.

cv - Rep RndForest Multinomial Xgboost Nnet
1 0,8585 0,8124 0,8846 0,8640
2 0,8584 0,8125 0,8860 0,8637
3 0,8581 0,8123 0,8842 0,8639
4 0,8581 0,8122 0,8838 0,8645
5 0,8582 0,8124 0,8850 0,8634
6 0,8579 0,8123 0,8850 0,8633
7 0,8586 0,8122 0,8841 0,8626
8 0,8584 0,8124 0,8845 0,8639
9 0,8585 0,8123 0,8850 0,8624
10 0,8584 0,8123 0,8838 0,8639
———- ———- ———- ———- ———-
Media 0,8583 0,8123 0,8846 0,8636
Mediana 0,8584 0,8123 0,8845 0,8638
StdD 0,0002 0,0001 0,0007 0,0006

Cuadro muestra un resumen de los resultados obtenidos para los 4 modelos Caret en el proceso de validación cruzada repetida (VCR) para 10 grupos. La primera columna muestra el grupo de VCR a que fue sometido el algoritmo, los valores en columnas muestran el accuracy obtenido sobre el set de datos de entrenamiento luego del proceso. Mejor modelo es Xgboost con 88,45% de accuracy en su mediana.

Anexo 33: Código R de preparación de datos para modelos en H2O.

# Carga de librerias a usar
library(h2o)
library(caret)
library(pROC)
library(dbplyr)
library(magrittr)
library(tidyverse)
library(ggpubr)
h2o.init()

#-----------------------------------------------------------------------
# Preparación datos entrenamiento
#-----------------------------------------------------------------------
# Cargamos datos
datosTrain<-readRDS("datosTrain")

# Preparamos el set de datos para trabajar con H2O. 
# Usamos las mejores variables definidas por Random Forest
x<-c(datosTrain[60],datosTrain[41],datosTrain[54],datosTrain[59],datosTrain[55],
     datosTrain[35],datosTrain[36],datosTrain[58],datosTrain[17],datosTrain[45],
     datosTrain[49],datosTrain[38],datosTrain[28],datosTrain[42],datosTrain[53],
     datosTrain[39],datosTrain[44],datosTrain[3],datosTrain[51],datosTrain[5],
     datosTrain[4],datosTrain[15],datosTrain[21],datosTrain[2],datosTrain[18],
     datosTrain[1],datosTrain[43],datosTrain[57],datosTrain[16],datosTrain[24])

# Transformamos a data frame de H2O
x<-as.data.frame(x)
y<-c(datosTrain[40])
train<-cbind(y,x)
train<-as.h2o(train)

#-----------------------------------------------------------------------
# Preparación datos validación
#-----------------------------------------------------------------------
# Cargamos datos
datosValidacion<-readRDS("datosValidacion")

# Preparamos el set de datos para trabajar con H2O. 
# Usamos las mejores variables definidas por Random Forest
xVal<-c(datosValidacion[60],datosValidacion[41],datosValidacion[54],datosValidacion[59],
        datosValidacion[55],datosValidacion[35],datosValidacion[36],datosValidacion[58],
        datosValidacion[17],datosValidacion[45],datosValidacion[49],datosValidacion[38],
        datosValidacion[28],datosValidacion[42],datosValidacion[53],datosValidacion[39],
        datosValidacion[44],datosValidacion[3],datosValidacion[51],datosValidacion[5],
        datosValidacion[4],datosValidacion[15],datosValidacion[21],datosValidacion[2],
        datosValidacion[18],datosValidacion[1],datosValidacion[43],datosValidacion[57],
        datosValidacion[16],datosValidacion[24])

# Transformamos a data frame de H2O
xVal<-as.data.frame(xVal)
yVal<-c(datosValidacion[40])
validacion<-cbind(yVal,xVal)
validacion<-as.h2o(validacion)

#-----------------------------------------------------------------------
# Preparación datos test
#-----------------------------------------------------------------------
# Cargamos datos
datosTest<-readRDS("datosTest")

# Preparamos el set de datos para trabajar con H2O. 
# Usamos las mejores variables definidas por Random Forest
xTest<-c(datosTest[60],datosTest[41],datosTest[54],datosTest[59],datosTest[55],
         datosTest[35],datosTest[36],datosTest[58],datosTest[17],datosTest[45],
         datosTest[49],datosTest[38],datosTest[28],datosTest[42],datosTest[53],
         datosTest[39],datosTest[44],datosTest[3],datosTest[51],datosTest[5],
         datosTest[4],datosTest[15],datosTest[21],datosTest[2],datosTest[18],
         datosTest[1],datosTest[43],datosTest[57],datosTest[16],datosTest[24])

# Transformamos a data frame de H2O
xTest<-as.data.frame(xTest)
yTest<-c(datosTest[40])
test<-cbind(yTest,xTest)
test<-as.h2o(test)



Anexo 34: Interfaz Flow de H2O.

Interfaz web H2O

Interfaz web H2O

Los modelos H2O fueron construidos sin código, y directamente desde la aplicación web de H2O: Flow. Previa modelización, es requisito inicializar el cluster en R por con h2o.init(). Por medio de esta acción, se habilita el acceso al puerto local #54321 en el browser.

En R se realizó el tratamiento previo de datos, es decir, su transformación de data frames de R a data frames de H2O. La interfaz, automáticamente detecta los datos transformados, con lo que se puede modelar directamente.

Anexo 35: Parámetros de modelo Gradient Boosting H2O.

label type level actual_value default_value
model_id Key critical Modelo GbmH2o_cv
nfolds int critical 10 0
keep_cv_models boolean expert true true
keep_cv_predictions boolean expert true false
keep_cv_fold_assignment boolean expert false false
score_each_iteration boolean secondary false false
score_tree_interval int secondary 0 0
fold_assignment enum secondary AUTO AUTO
response_column VecSpecifier critical varObj
ignore_const_cols boolean critical true true
balance_classes boolean secondary false false
max_after_balance_size float expert 5 5
max_confusion_matrix_size int secondary 20 20
max_hit_ratio_k int secondary 0 0
ntrees int critical 375 50
max_depth int critical 60 5
min_rows double critical 10 10
nbins int critical 20 20
nbins_top_level int secondary 1024 1024
nbins_cats int secondary 1024 1024
r2_stopping double secondary 1,79e+308 1,79e+308
stopping_rounds int secondary 0 0
stopping_metric enum secondary AUTO AUTO
stopping_tolerance double secondary 0.001 0.001
max_runtime_secs double secondary 0 0
seed long critical 4321 -1
build_tree_one_node boolean expert false false
learn_rate double critical 0.01 0.1
learn_rate_annealing double secondary 1 1
distribution enum secondary multinomial AUTO
quantile_alpha double secondary 0.5 0.5
tweedie_power double secondary 1.5 1.5
huber_alpha double secondary 0.9 0.9
sample_rate double critical 1 1
col_sample_rate double critical 1 1
col_sample_rate_change_per_level double expert 1 1
col_sample_rate_per_tree double secondary 1 1
min_split_improvement double secondary 0.00001 0.00001
histogram_type enum secondary AUTO AUTO
max_abs_leafnode_pred double expert 1.79e+308 1.79e+308
pred_noise_bandwidth double expert 0 0
categorical_encoding enum secondary AUTO AUTO
calibrate_model boolean expert false false
check_constant_response boolean expert true true

Cuadro muestra los parámetros usados y los por defecto, al correr modelo Gradient Boosting en librería H2O.

Anexo 36: Parámetros del modelo Deep Learning 2 H2O.

label type level actual_value default_value
model_id Key critical deepLearning2_cv
nfolds int critical 10 0
keep_cv_models boolean expert false true
keep_cv_predictions boolean expert true false
keep_cv_fold_assignment boolean expert false false
fold_assignment enum secondary AUTO AUTO
response_column VecSpecifier critical varObj
ignore_const_cols boolean critical false true
score_each_iteration boolean secondary false false
balance_classes boolean secondary false false
max_after_balance_size float expert 5 5
max_confusion_matrix_size int secondary 3 20
max_hit_ratio_k int secondary 0 0
overwrite_with_best_model boolean expert false true
use_all_factor_levels boolean secondary false true
standardize boolean secondary false true
activation enum critical Tanh Rectifier
hidden int[] critical 512 200200
epochs double critical 13,62 10
train_samples_per_iteration long secondary -2 -2
target_ratio_comm_to_comp double expert -2 0.05
seed long expert 4321 -1
adaptive_rate boolean secondary false true
rho double expert 0.99 0.99
epsilon double expert 1,00E-08 1,00E-08
rate double expert 0.0001 0.005
rate_annealing double expert 0.000001 0.000001
rate_decay double expert 0 1
momentum_start double expert 0.5 0
momentum_ramp double expert 1000000 1000000
momentum_stable double expert 0.99 0
nesterov_accelerated_gradient boolean expert true true
input_dropout_ratio double secondary 0 0
l1 double secondary 0 0
l2 double secondary 0 0
max_w2 float expert 3,40E+45 3,40E+45
initial_weight_distribution enum expert UniformAdaptive UniformAdaptive
initial_weight_scale double expert 1 1
loss enum secondary CrossEntropy Automatic
distribution enum secondary multinomial AUTO
quantile_alpha double secondary 0.5 0.5
tweedie_power double secondary 1.5 1.5
huber_alpha double secondary 0.9 0.9
score_interval double secondary 5 5
score_training_samples long secondary 10000 10000
score_validation_samples long secondary 2500 0
score_duty_cycle double secondary 0.1 0.1
classification_stop double expert 0 0
regression_stop double expert 0.000001 0.000001
stopping_rounds int secondary 0 5
stopping_metric enum secondary logloss AUTO
stopping_tolerance double secondary 0.001 0
max_runtime_secs double secondary 0 0
score_validation_sampling enum expert Stratified Uniform
diagnostics boolean expert true true
fast_mode boolean expert true true
force_load_balance boolean expert true true
variable_importances boolean critical false true
replicate_training_data boolean secondary true true
single_node_mode boolean expert false false
shuffle_training_data boolean expert false false
missing_values_handling enum expert MeanImputation MeanImputation
quiet_mode boolean expert false false
autoencoder boolean secondary false false
sparse boolean expert false false
col_major boolean expert false false
average_activation double expert 0 0
sparsity_beta double expert 0 0
max_categorical_features int expert 2147483647 2147483647
reproducible boolean expert false false
export_weights_and_biases boolean expert false false
mini_batch_size int expert 1 1
categorical_encoding enum secondary AUTO AUTO
elastic_averaging boolean expert false false
elastic_averaging_moving_rate double expert 0.9 0.9
elastic_averaging_regularization double expert 0.001 0.001

Cuadro muestra los parámetros usados y los por defecto, al correr modelo Deep Learning 2, en librería H2O.

Anexo 37: Resumen de parámetros de modelo Deep Learning 2 de H2O.

Configuración de Deep Learning 2 H2O

Configuración de Deep Learning 2 H2O

Cuadro muestra la estructura de la red del modelo Deep Learning 2, con solo 1 capa oculta con 512 neuronas.

Anexo 38: Parámetros del modelo Deep Learning 4 H2O.

label type level actual_value default_value
model_id Key critical deepLearning4_cv
nfolds int critical 10 0
keep_cv_models boolean expert false true
keep_cv_predictions boolean expert true false
keep_cv_fold_assignment boolean expert false false
fold_assignment enum secondary AUTO AUTO
response_column VecSpecifier critical varObj
ignore_const_cols boolean critical false true
score_each_iteration boolean secondary false false
balance_classes boolean secondary false false
max_after_balance_size float expert 5 5
max_confusion_matrix_size int secondary 3 20
max_hit_ratio_k int secondary 0 0
overwrite_with_best_model boolean expert false true
use_all_factor_levels boolean secondary false true
standardize boolean secondary false true
activation enum critical Tanh Rectifier
hidden int[] critical 32,32,32,32,32 200,200
epochs double critical 20,5 10
train_samples_per_iteration long secondary -2 -2
target_ratio_comm_to_comp double expert -2 0.05
seed long expert 4321 -1
adaptive_rate boolean secondary false true
rho double expert 0.99 0.99
epsilon double expert 1,00E-08 1,00E-08
rate double expert 0.0001 0.005
rate_annealing double expert 0.000001 0.000001
rate_decay double expert 0 1
momentum_start double expert 0.5 0
momentum_ramp double expert 1000000 1000000
momentum_stable double expert 0.99 0
nesterov_accelerated_gradient boolean expert true true
input_dropout_ratio double secondary 0 0
l1 double secondary 0 0
l2 double secondary 0 0
max_w2 float expert 3,40E+45 3,40E+45
initial_weight_distribution enum expert UniformAdaptive UniformAdaptive
initial_weight_scale double expert 1 1
loss enum secondary CrossEntropy Automatic
distribution enum secondary multinomial AUTO
quantile_alpha double secondary 0.5 0.5
tweedie_power double secondary 1.5 1.5
huber_alpha double secondary 0.9 0.9
score_interval double secondary 5 5
score_training_samples long secondary 10000 10000
score_validation_samples long secondary 2500 0
score_duty_cycle double secondary 0.1 0.1
classification_stop double expert 0 0
regression_stop double expert 0.000001 0.000001
stopping_rounds int secondary 0 5
stopping_metric enum secondary logloss AUTO
stopping_tolerance double secondary 0.001 0
max_runtime_secs double secondary 0 0
score_validation_sampling enum expert Stratified Uniform
diagnostics boolean expert true true
fast_mode boolean expert true true
force_load_balance boolean expert true true
variable_importances boolean critical false true
replicate_training_data boolean secondary true true
single_node_mode boolean expert false false
shuffle_training_data boolean expert false false
missing_values_handling enum expert MeanImputation MeanImputation
quiet_mode boolean expert false false
autoencoder boolean secondary false false
sparse boolean expert false false
col_major boolean expert false false
average_activation double expert 0 0
sparsity_beta double expert 0 0
max_categorical_features int expert 2147483647 2147483647
reproducible boolean expert false false
export_weights_and_biases boolean expert false false
mini_batch_size int expert 1 1
categorical_encoding enum secondary AUTO AUTO
elastic_averaging boolean expert false false
elastic_averaging_moving_rate double expert 0.9 0.9
elastic_averaging_regularization double expert 0.001 0.001

Cuadro muestra los parámetros usados y los por defecto, al correr modelo Deep Learning 4 en librería H2O.

Anexo 39: Resumen de parámetros de modelo Deep Learning 4 de H2O.

Configuración de Deep Learning 4 H2O

Configuración de Deep Learning 4 H2O

Cuadro muestra la estructura de la red del modelo Deep Learning 4, con 5 capas ocultas con 32 neuronas cada una.

Anexo 40: Evolución de funciones de error, modelo Gradient Boosting en H2O.

Funciones de error Train/Validación GBM H2O - Azul:Train, Naranja:Validación

Funciones de error Train/Validación GBM H2O - Azul:Train, Naranja:Validación

La gráfica de los errores en Gradient Boosting muestra que ellos se reducen en la medida que avanza el número de iteraciones. la baja es continua. La gráfica naranja, que muestra el error en el set e datos de validación no muestra incrementos, por lo que es indicativo de la no existencia de sobreajuste. Modelo es detenido su entrenamiento a las 375 iteraciones.

Anexo 41: Evolución de funciones de error, modelo Deep Learning 2 en H2O.

Funciones de error Train/Validación Deep Learning 2

Funciones de error Train/Validación Deep Learning 2

Gráfica de los errores para el modelo de Deep Learning 2 -1 capa oculta con 512 neuronas-, muestra alta variación a medida que avanzan las iteraciones. Las gráficas de entrenamiento y validación se mueven juntas.

Anexo 42: Evolución de funciones de error, modelo Deep Learning 4 en H2O.

Funciones de error Train/Validación Deep Learning 4

Funciones de error Train/Validación Deep Learning 4

Gráfica de los errores para el modelo de Deep Learning 4 -5 capas ocultas con 32 neuronas en cada una-, muestra un patrón de errores decreciente a medida que avanzan las iteraciones. Curva de entrenamiento y validación, se mueven juntas. Al comparar versus la gráfica de Deep Learing 2, vemos que es preferible más capas ocultas con menos neuronas, que menos capas ocultas con un mayor número de neuronas.

Anexo 43: Resultados de accuracy, validación Cruzada Repetida modelos H2O.

cv - Rep GBM Deep Learn 2 Deep Learn 4 Stack GLM Stack RF Stack GBM
cv_1_valid 0,8816 0,7718 0,7884 0,8945 0,8932 0,8975
cv_2_valid 0,8870 0,8381 0,8785 0,8869 0,8856 0,8874
cv_3_valid 0,8871 0,8571 0,8529 0,8927 0,8856 0,8902
cv_4_valid 0,8856 0,8182 0,8471 0,8947 0,8796 0,8919
cv_5_valid 0,8854 0,8000 0,8154 0,8895 0,8874 0,8878
cv_6_valid 0,8856 0,8238 0,8320 0,8961 0,9029 0,8970
cv_7_valid 0,8890 0,8411 0,8566 0,8972 0,8968 0,8943
cv_8_valid 0,8938 0,8857 0,8898 0,8898 0,8866 0,8902
cv_9_valid 0,8912 0,8247 0,8526 0,8967 0,8902 0,8945
cv_10_valid 0,8879 0,8178 0,8340 0,8963 0,8971 0,8930
————- ————- ————- ————- ————- ————- ————-
mean 0,8874 0,8278 0,8447 0,8934 0,8905 0,8924
median 0,8871 0,8247 0,8471 0,8945 0,8902 0,8924
sd 0,0034 0,0310 0,0294 0,0036 0,0069 0,0035

Cuadro muestra un resumen de los resultados obtenidos para los 6 modelos de la librería H2O en el proceso de validación cruzada repetida (VCR) para 10 grupos. La primera columna muestra el grupo de VCR a que fue sometido el algoritmo, los valores en columnas muestran el accuracy obtenido sobre el set de datos de entrenamiento luego del proceso. Mejor modelo es el ensamblado con GLM, con un 89,45% de accuracy en su mediana.

Anexo 44: Evolución de funciones de error, modelos ensamblados en H2O.

Gráficas de error para los modelos ensamblados.

Gráficas de error para los modelos ensamblados.

Gráfica de las funciones de error para los modelos ensamblados. Es posible ver la bondad de estos modelos dado que incorporan la estabilidad del modelo Gradient Boosting, suavizando las curvas de errores a lo largo de las iteraciones.


  1. Media pensión.

  2. Pensión completa.

  3. Por confidencialidad de la información, no se entrega detalle de qué tipo de habitación se trata cada letra.

  4. Por confidencialidad de la información, no se entrega detalle de qué tipo de habitación se trata cada letra.

  5. Cliente individual en estadía corta.

  6. Cliente en estadía corta acompañado.

  7. Cliente con tarifa corporativa.

  8. Reserva masiva.

  9. El diagrama que muestra la distribución por países de los viajeros que realizan reservas de hotel en la base puede consultarse en el anexo 1.

  10. Revisar anexo 2 con gráficas del perfil básico del viajero de la base de datos.

  11. No se queda los fines de semana, dado que vuelve a casa. Esta es otra razón para considerar al viajero promedio como un viajero de negocios.

  12. El pago refundable lo interpretamos como aquel cargo que se realiza con la tarjeta de crédito, y que luego es reversado al momento del check-out.

  13. Revisar anexos 3 y 4 con estadísticas del viajero, desde el punto de vista del negocio hotelero.

  14. Ver Anexo 5 con cuadro detalle.

  15. Ver anexo 6.

  16. Ver anexo 7 con gráfica de nuestra variable objetivo.

  17. Si es así, por qué motivo ?.

  18. Ver anexo 8 con este detalle.

  19. Ver anexo 9.

  20. Ver anexo 10.

  21. Ver anexo 11.

  22. Ver anexo 12 con las gráficas para las cancelaciones por mes y día.

  23. Recordemos que no se tiene información del origen de la base de datos.

  24. Se descartó el uso de la métrica AUC (área bajo la curva ROC) por cuánto nuestro set de datos es multiclase, y el AUC solo está definido para sets binarios. No obstante existe la función multiclassRoc() en R, verificamos que ésta no genera resultados consistentes entre los distintos sets.

  25. Revisar anexo 13 con código R para la depuración de datos.

  26. Revisar anexo 14 con código R para feature engineering.

  27. Revisar anexo 15 con gráfica de correlaciones entre las variables numéricas.

  28. En los anexos 16 y 17 se pueden observar las gráficas de las variables antes y después de sus transformaciones.

  29. El detalle completo de las variables utilizadas, puede verse en el anexo 18.

  30. Ver anexo 19 con código R para los modelos Random Forest.

  31. Recordar que los porcentajes fueron Check-out 63%, Cancelación 36% y No presentación 1%.

  32. Revisar anexos 20 y 21 con desempeño de accuracy en modelos 1 y 2.

  33. Ver las matrices de confusión de RStudio para el modelo 3 en los anexos 22 y 23.

  34. Revisar punto de selección de variables.

  35. Al reducir el número de variables debe cambiar el mtry. En este caso consideramos el sqrt(# variables).

  36. Ver anexo 24 con código R para los modelos Multinomial.

  37. Ver anexo 25 con código R para los modelos Xgboost.

  38. Revisar anexo 26 con gráfica del modelo 1 para el tunning de hiperparámetros.

  39. La gráfica de este entrenamiento puede verse en el anexo 27.

  40. Parámetros eta = 0.05, min_child_weight=5 y nrounds=3000.

  41. Ver anexo 28 con código R para los modelos de redes neuronales de Caret.

  42. Número de nodos -neuronas- en capa oculta,

  43. Tasa de aprendizaje - learning rate.

  44. 30 nodos en capa input, 20 nodos en capa intermedia y 3 nodos en capa output. Para todas las capas, la función de activación fue softmax.

  45. Las matrices de confusión pueden verse en los anexos 29 y 30.

  46. Samsung Galaxy Book 12, i5-7200, 8Gb RAM y w10x64.

  47. Máquina virtual n1-standard-16: 16 CPU y 60Gb RAM.

  48. Código de R para proceso de validación cruzada de los modelos Caret, puede verse en el anexo 31.

  49. Si el algoritmo lo requería.

  50. La tabla de base para boxplot() puede verse en el anexo 32.

  51. Recordar que sobre el set de validación obtuvo un accuracy de un 88,9%.

  52. El código de preparación de los datos para ser usados en H2O, puede consultarse en el anexo 33.

  53. Un print de la interfaz Flow, puede verse en el anexo 34.

  54. Entre otras ventajas de H2O, podemos mencionar que permite el entrenamiento de un algoritmo en tiempo real, a diferencia de Caret, donde los algoritmos se visualizan una vez entrenados. Esto permite detener / mejorar un modelo, si el procesamiento de datos toma mucho tiempo.

  55. Recordar que en Caret no fue posible su desarrollo.

  56. Detalle de los parámetros seleccionados para correr el modelo GBM pueden verse en el anexo 35.

  57. Revisar gráfica de errores en anexo 40.

  58. En cada configuración, el primer valor corresponde al número de neuronas en la capa input, el último, a al número de neuronas en la capa output. Los valores intermedios, corresponden al número de neuronas en las capas ocultas.

  59. Ver anexos 36 a 39 con detalle de los parámetros usados para correr ambas redes.

  60. Revisar anexo 41 con gráfica de errores para el modelo Deep Learning 2 de H2O.

  61. Ver anexo 42 con gráfica de errores para modelo Deep Learing 4 con H2O.

  62. Ver anexo 44 con gráficas de funciones de error para cada ensamblado.

  63. Ver anexo 43 con datos de origen para la gráfica del proceso de VCR en H2O.

  64. App 88,5% en Xgboots vs 89,5% en stacked GLM.